Update: Full E2EE + Scripts
This commit is contained in:
@@ -2,37 +2,30 @@
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private bool _openedSecondWindow;
|
||||
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var username = Environment.GetCommandLineArgs()
|
||||
.Skip(1)
|
||||
.Chunk(2)
|
||||
.Where(x => x.Length == 2 && x[0] == "--user")
|
||||
.Select(x => x[1])
|
||||
.FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
throw new Exception("Missing required --user argument. Example: --user Keeper317");
|
||||
}
|
||||
|
||||
ClientSession.Username = username;
|
||||
}
|
||||
|
||||
protected override Window CreateWindow(IActivationState? activationState)
|
||||
{
|
||||
var keeperWindow = new Window(new MainPage("Keeper317"))
|
||||
return new Window(new MainPage(ClientSession.Username))
|
||||
{
|
||||
Title = "Relay Client - Keeper317"
|
||||
Title = $"Relay Client - {ClientSession.Username}"
|
||||
};
|
||||
|
||||
keeperWindow.Created += KeeperWindow_Created;
|
||||
|
||||
return keeperWindow;
|
||||
}
|
||||
|
||||
private void KeeperWindow_Created(object? sender, EventArgs e)
|
||||
{
|
||||
if (_openedSecondWindow)
|
||||
return;
|
||||
|
||||
_openedSecondWindow = false;
|
||||
|
||||
var kiraWindow = new Window(new MainPage("Ru_Kira"))
|
||||
{
|
||||
Title = "Relay Client - Ru_Kira"
|
||||
};
|
||||
|
||||
Current?.OpenWindow(kiraWindow);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,5 @@
|
||||
namespace RelayClient;
|
||||
|
||||
public static class ChatSimulator
|
||||
{
|
||||
public static event Action<ChatMessage>? MessageSent;
|
||||
|
||||
public static void Send(string senderUsername, string text)
|
||||
{
|
||||
var message = new ChatMessage
|
||||
{
|
||||
SenderUsername = senderUsername,
|
||||
Text = text,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
|
||||
MessageSent?.Invoke(message);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChatMessage
|
||||
{
|
||||
public required string SenderUsername { get; set; }
|
||||
|
||||
6
RelayClient/ClientSession.cs
Normal file
6
RelayClient/ClientSession.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace RelayClient;
|
||||
|
||||
public static class ClientSession
|
||||
{
|
||||
public static string Username { get; set; } = "Unknown";
|
||||
}
|
||||
@@ -9,35 +9,33 @@ public static class E2EeHelper
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
|
||||
var publicKey = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo());
|
||||
var privateKey = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey());
|
||||
|
||||
return (publicKey, privateKey);
|
||||
return (
|
||||
Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()),
|
||||
Convert.ToBase64String(rsa.ExportPkcs8PrivateKey())
|
||||
);
|
||||
}
|
||||
|
||||
public static EncryptedMessagePayload EncryptForRecipient(string plainText, string recipientPublicKeyBase64)
|
||||
public static EncryptedPayload EncryptForRecipient(string plainText, string recipientPublicKeyBase64)
|
||||
{
|
||||
var aesKey = RandomNumberGenerator.GetBytes(32);
|
||||
var nonce = RandomNumberGenerator.GetBytes(12);
|
||||
var plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
var cipherBytes = new byte[plainBytes.Length];
|
||||
var tag = new byte[16];
|
||||
byte[] aesKey = RandomNumberGenerator.GetBytes(32);
|
||||
byte[] nonce = RandomNumberGenerator.GetBytes(12);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = new byte[plainBytes.Length];
|
||||
byte[] tag = new byte[16];
|
||||
|
||||
using (var aes = new AesGcm(aesKey, 16))
|
||||
{
|
||||
aes.Encrypt(nonce, plainBytes, cipherBytes, tag);
|
||||
}
|
||||
|
||||
var recipientPublicKey = Convert.FromBase64String(recipientPublicKeyBase64);
|
||||
byte[] encryptedKey;
|
||||
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
rsa.ImportSubjectPublicKeyInfo(recipientPublicKey, out _);
|
||||
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(recipientPublicKeyBase64), out _);
|
||||
encryptedKey = rsa.Encrypt(aesKey, RSAEncryptionPadding.OaepSHA256);
|
||||
}
|
||||
|
||||
return new EncryptedMessagePayload
|
||||
return new EncryptedPayload
|
||||
{
|
||||
CipherText = Convert.ToBase64String(cipherBytes),
|
||||
Nonce = Convert.ToBase64String(nonce),
|
||||
@@ -46,34 +44,32 @@ public static class E2EeHelper
|
||||
};
|
||||
}
|
||||
|
||||
public static string DecryptForRecipient(EncryptedMessagePayload payload, string recipientPrivateKeyBase64)
|
||||
public static string DecryptForRecipient(EncryptedPayload payload, string recipientPrivateKeyBase64)
|
||||
{
|
||||
var encryptedKey = Convert.FromBase64String(payload.EncryptedKey);
|
||||
var privateKey = Convert.FromBase64String(recipientPrivateKeyBase64);
|
||||
|
||||
byte[] aesKey;
|
||||
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
rsa.ImportPkcs8PrivateKey(privateKey, out _);
|
||||
aesKey = rsa.Decrypt(encryptedKey, RSAEncryptionPadding.OaepSHA256);
|
||||
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(recipientPrivateKeyBase64), out _);
|
||||
aesKey = rsa.Decrypt(Convert.FromBase64String(payload.EncryptedKey), RSAEncryptionPadding.OaepSHA256);
|
||||
}
|
||||
|
||||
var nonce = Convert.FromBase64String(payload.Nonce);
|
||||
var tag = Convert.FromBase64String(payload.Tag);
|
||||
var cipherBytes = Convert.FromBase64String(payload.CipherText);
|
||||
var plainBytes = new byte[cipherBytes.Length];
|
||||
byte[] plainBytes = new byte[Convert.FromBase64String(payload.CipherText).Length];
|
||||
|
||||
using (var aes = new AesGcm(aesKey, 16))
|
||||
{
|
||||
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
|
||||
aes.Decrypt(
|
||||
Convert.FromBase64String(payload.Nonce),
|
||||
Convert.FromBase64String(payload.CipherText),
|
||||
Convert.FromBase64String(payload.Tag),
|
||||
plainBytes
|
||||
);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public class EncryptedMessagePayload
|
||||
public class EncryptedPayload
|
||||
{
|
||||
public required string CipherText { get; set; }
|
||||
public required string Nonce { get; set; }
|
||||
|
||||
@@ -2,19 +2,36 @@ namespace RelayClient.Crypto;
|
||||
|
||||
public static class KeyStorage
|
||||
{
|
||||
private static string GetKeyFolder()
|
||||
{
|
||||
var folder = Path.Combine(FileSystem.AppDataDirectory, "keys");
|
||||
Directory.CreateDirectory(folder);
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static void SavePrivateKey(string username, string privateKey)
|
||||
{
|
||||
Directory.CreateDirectory("keys");
|
||||
File.WriteAllText(Path.Combine("keys", $"{username}.private.key"), privateKey);
|
||||
File.WriteAllText(Path.Combine(GetKeyFolder(), $"{username}.private.key"), privateKey);
|
||||
}
|
||||
|
||||
public static void SavePublicKey(string username, string publicKey)
|
||||
{
|
||||
File.WriteAllText(Path.Combine(GetKeyFolder(), $"{username}.public.key"), publicKey);
|
||||
}
|
||||
|
||||
public static string LoadPrivateKey(string username)
|
||||
{
|
||||
return File.ReadAllText(Path.Combine("keys", $"{username}.private.key"));
|
||||
return File.ReadAllText(Path.Combine(GetKeyFolder(), $"{username}.private.key"));
|
||||
}
|
||||
|
||||
public static bool PrivateKeyExists(string username)
|
||||
public static string LoadPublicKey(string username)
|
||||
{
|
||||
return File.Exists(Path.Combine("keys", $"{username}.private.key"));
|
||||
return File.ReadAllText(Path.Combine(GetKeyFolder(), $"{username}.public.key"));
|
||||
}
|
||||
|
||||
public static bool HasKeys(string username)
|
||||
{
|
||||
return File.Exists(Path.Combine(GetKeyFolder(), $"{username}.private.key")) &&
|
||||
File.Exists(Path.Combine(GetKeyFolder(), $"{username}.public.key"));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
namespace RelayClient;
|
||||
using RelayClient.Crypto;
|
||||
using RelayClient.Models;
|
||||
using WebSocketSharp;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RelayClient;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
private readonly string _username;
|
||||
private readonly WebSocket wsc;
|
||||
private string? _serverPublicKey;
|
||||
|
||||
public MainPage(string username)
|
||||
{
|
||||
@@ -11,39 +18,134 @@ public partial class MainPage : ContentPage
|
||||
_username = username;
|
||||
UserLabel.Text = $"Logged in as: {_username}";
|
||||
|
||||
ChatSimulator.MessageSent += OnMessageSent;
|
||||
if (!KeyStorage.HasKeys(_username))
|
||||
{
|
||||
var keys = E2EeHelper.GenerateRsaKeyPair();
|
||||
KeyStorage.SavePrivateKey(_username, keys.privateKey);
|
||||
KeyStorage.SavePublicKey(_username, keys.publicKey);
|
||||
}
|
||||
|
||||
wsc = new WebSocket("ws://localhost:1337/");
|
||||
|
||||
wsc.OnMessage += WscOnMessage;
|
||||
wsc.Connect();
|
||||
|
||||
var publicKey = KeyStorage.LoadPublicKey(_username);
|
||||
wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
|
||||
wsc.Send("GET_SERVER_KEY");
|
||||
wsc.Send($"GET_HISTORY|{_username}");
|
||||
}
|
||||
|
||||
private void SendButton_OnClicked(object? sender, EventArgs e)
|
||||
{
|
||||
SendMessage(sender, e);
|
||||
SendMessage();
|
||||
}
|
||||
|
||||
private void MessageEntry_OnCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
SendMessage(sender, e);
|
||||
SendMessage();
|
||||
}
|
||||
|
||||
private void SendMessage(object? sender, EventArgs e)
|
||||
private void SendMessage()
|
||||
{
|
||||
var text = MessageEntry.Text?.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
MauiProgram.wsc.Send($"{_username}:{text}");
|
||||
if (string.IsNullOrWhiteSpace(_serverPublicKey))
|
||||
{
|
||||
Console.WriteLine("Server public key not loaded yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ChatSimulator.Send(_username, text);
|
||||
var encrypted = E2EeHelper.EncryptForRecipient(text, _serverPublicKey);
|
||||
|
||||
Console.WriteLine($"[{_username}] sent message: {text}");
|
||||
var payload = new SocketEncryptedMessage
|
||||
{
|
||||
Type = "client_encrypted_chat",
|
||||
SenderUsername = _username,
|
||||
CipherText = encrypted.CipherText,
|
||||
Nonce = encrypted.Nonce,
|
||||
Tag = encrypted.Tag,
|
||||
EncryptedKey = encrypted.EncryptedKey
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
wsc.Send(json);
|
||||
|
||||
Console.WriteLine($"[{_username}] sent encrypted message.");
|
||||
|
||||
MessageEntry.Text = string.Empty;
|
||||
MessageEntry.Focus();
|
||||
}
|
||||
|
||||
private void OnMessageSent(ChatMessage message)
|
||||
private void WscOnMessage(object? sender, MessageEventArgs e)
|
||||
{
|
||||
if (e.Data.StartsWith("SERVER:REGISTERED_KEY:"))
|
||||
{
|
||||
Console.WriteLine(e.Data);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{_username}] RAW WS DATA: {e.Data}");
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(e.Data);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (!root.TryGetProperty("Type", out var typeElement))
|
||||
return;
|
||||
|
||||
var type = typeElement.GetString();
|
||||
|
||||
if (type == "server_public_key")
|
||||
{
|
||||
var serverKeyMessage = JsonSerializer.Deserialize<ServerPublicKeyMessage>(e.Data);
|
||||
if (serverKeyMessage is not null)
|
||||
{
|
||||
_serverPublicKey = serverKeyMessage.PublicKey;
|
||||
Console.WriteLine($"[{_username}] loaded server public key.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != "encrypted_chat")
|
||||
return;
|
||||
|
||||
var payload = JsonSerializer.Deserialize<SocketEncryptedMessage>(e.Data);
|
||||
if (payload is null)
|
||||
return;
|
||||
|
||||
if (payload.RecipientUsername != _username)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[{_username}] received encrypted payload for {payload.RecipientUsername}");
|
||||
|
||||
var privateKey = KeyStorage.LoadPrivateKey(_username);
|
||||
|
||||
var decryptedText = E2EeHelper.DecryptForRecipient(
|
||||
new EncryptedPayload
|
||||
{
|
||||
CipherText = payload.CipherText,
|
||||
Nonce = payload.Nonce,
|
||||
Tag = payload.Tag,
|
||||
EncryptedKey = payload.EncryptedKey
|
||||
},
|
||||
privateKey
|
||||
);
|
||||
|
||||
Console.WriteLine($"[{_username}] decrypted message from {payload.SenderUsername}: {decryptedText}");
|
||||
|
||||
var message = new ChatMessage
|
||||
{
|
||||
SenderUsername = payload.SenderUsername,
|
||||
Text = decryptedText,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
bool isOwnMessage = message.SenderUsername == _username;
|
||||
@@ -87,10 +189,16 @@ public partial class MainPage : ContentPage
|
||||
await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{_username}] failed to process websocket message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
ChatSimulator.MessageSent -= OnMessageSent;
|
||||
wsc.OnMessage -= WscOnMessage;
|
||||
wsc.Close();
|
||||
base.OnDisappearing();
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,10 @@ namespace RelayClient;
|
||||
public static class MauiProgram
|
||||
{
|
||||
// public static event Action<ChatMessage>? MessageSent;
|
||||
public static WebSocket wsc = new WebSocket("ws://localhost:1337");
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
wsc.OnMessage += (sender, e) => OnWebSocketRecieved(sender, e);
|
||||
wsc.Connect();
|
||||
//wsc.OnMessage += (sender, e) => OnWebSocketRecieved(sender, e);
|
||||
//wsc.Connect();
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder.UseMauiApp<App>().ConfigureFonts(fonts =>
|
||||
{
|
||||
@@ -29,19 +28,18 @@ public static class MauiProgram
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static void OnWebSocketRecieved(object? sender, MessageEventArgs e)
|
||||
{
|
||||
Console.WriteLine(sender.ToString());
|
||||
|
||||
ChatSimulator.Send(e.Data.Split(":")[0], e.Data.Split(":")[1]);
|
||||
|
||||
// var message = new ChatMessage
|
||||
// {
|
||||
// SenderUsername = e.Data.Split(":")[0],
|
||||
// Text = e.Data.Split(":")[1],
|
||||
// Timestamp = DateTime.Now
|
||||
// };
|
||||
//public static void OnWebSocketRecieved(object? sender, MessageEventArgs e)
|
||||
//{
|
||||
// Console.WriteLine(sender.ToString());
|
||||
//
|
||||
// MessageSent?.Invoke(message);
|
||||
}
|
||||
// ChatSimulator.Send(e.Data.Split(":")[0], e.Data.Split(":")[1]);
|
||||
// // var message = new ChatMessage
|
||||
// // {
|
||||
// // SenderUsername = e.Data.Split(":")[0],
|
||||
// // Text = e.Data.Split(":")[1],
|
||||
// // Timestamp = DateTime.Now
|
||||
// // };
|
||||
// //
|
||||
// // MessageSent?.Invoke(message);
|
||||
//}
|
||||
}
|
||||
7
RelayClient/Models/ServerPublicKeyMessage.cs
Normal file
7
RelayClient/Models/ServerPublicKeyMessage.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RelayClient.Models;
|
||||
|
||||
public class ServerPublicKeyMessage
|
||||
{
|
||||
public required string Type { get; set; }
|
||||
public required string PublicKey { get; set; }
|
||||
}
|
||||
12
RelayClient/Models/SocketEncryptedMessage.cs
Normal file
12
RelayClient/Models/SocketEncryptedMessage.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace RelayClient.Models;
|
||||
|
||||
public class SocketEncryptedMessage
|
||||
{
|
||||
public required string Type { get; set; }
|
||||
public required string SenderUsername { get; set; }
|
||||
public string? RecipientUsername { get; set; }
|
||||
public required string CipherText { get; set; }
|
||||
public required string Nonce { get; set; }
|
||||
public required string Tag { get; set; }
|
||||
public required string EncryptedKey { get; set; }
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using SurrealDb.Net;
|
||||
using SurrealDb.Net.Models;
|
||||
using SurrealDb.Net.Models.Auth;
|
||||
using System.Text.Json;
|
||||
using RelayCore;
|
||||
using RelayCore.Enums;
|
||||
using RelayCore.Models;
|
||||
|
||||
@@ -13,9 +11,11 @@ await db.Use("test", "test");
|
||||
|
||||
var keeper = await CreateUserAsync(db, "Keeper317", "Keeper317@gmail.com", "password");
|
||||
var kira = await CreateUserAsync(db, "Ru_Kira", "jduesling13@gmail.com", "password");
|
||||
var test = await CreateUserAsync(db, "Test", "test@gmail.com", "password");
|
||||
|
||||
Console.WriteLine($"Keeper created: {ToJsonString(keeper)}");
|
||||
Console.WriteLine($"Kira created: {ToJsonString(kira)}");
|
||||
Console.WriteLine($"Test created: {ToJsonString(test)}");
|
||||
Console.ReadKey(true);
|
||||
return;
|
||||
|
||||
|
||||
11
RelayServer/Models/ClientPublicKeys.cs
Normal file
11
RelayServer/Models/ClientPublicKeys.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using SurrealDb.Net.Models;
|
||||
|
||||
namespace RelayServer.Models;
|
||||
|
||||
public class ClientPublicKeys : Record
|
||||
{
|
||||
public required string Username { get; set; }
|
||||
public required string PublicKey { get; set; }
|
||||
public required DateTime CreatedAt { get; set; }
|
||||
public required DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace RelayServer.Models;
|
||||
public class ServerEncryptionKeys : Record
|
||||
{
|
||||
public required string KeyBase64 { get; set; }
|
||||
public required string PublicKey { get; set; }
|
||||
public required string PrivateKey { get; set; }
|
||||
public required DateTime CreatedAt { get; set; }
|
||||
public required DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
7
RelayServer/Models/ServerPublicKeyMessage.cs
Normal file
7
RelayServer/Models/ServerPublicKeyMessage.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RelayServer.Models;
|
||||
|
||||
public class ServerPublicKeyMessage
|
||||
{
|
||||
public required string Type { get; set; }
|
||||
public required string PublicKey { get; set; }
|
||||
}
|
||||
12
RelayServer/Models/SocketEncryptedMessage.cs
Normal file
12
RelayServer/Models/SocketEncryptedMessage.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace RelayServer.Models;
|
||||
|
||||
public class SocketEncryptedMessage
|
||||
{
|
||||
public required string Type { get; set; }
|
||||
public required string SenderUsername { get; set; }
|
||||
public required string RecipientUsername { get; set; }
|
||||
public required string CipherText { get; set; }
|
||||
public required string Nonce { get; set; }
|
||||
public required string Tag { get; set; }
|
||||
public required string EncryptedKey { get; set; }
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System;
|
||||
using WebSocketSharp.Server;
|
||||
using WebSocketSharp;
|
||||
|
||||
using RelayServer.Models;
|
||||
using RelayServer.Services;
|
||||
using WebSocketSharp.Server;
|
||||
using RelayServer.Models;
|
||||
|
||||
var surrealService = new SurrealService();
|
||||
var coreClient = new CoreClientService();
|
||||
@@ -12,23 +9,25 @@ var cryptoService = new ChannelCryptoService();
|
||||
|
||||
await using var db = await surrealService.ConnectAsync();
|
||||
|
||||
ChatTest.ClientKeyService = new ClientKeyService(db);
|
||||
ChatTest.Db = db;
|
||||
|
||||
var wssv = new WebSocketServer("ws://localhost:1337");
|
||||
wssv.AddWebSocketService<ChatTest>("/");
|
||||
wssv.Start();
|
||||
Console.WriteLine("WebSocket server started");
|
||||
Console.ReadKey(true);
|
||||
wssv.Stop();
|
||||
|
||||
var keeper = await coreClient.GetUserByUsernameAsync("Keeper317");
|
||||
var kira = await coreClient.GetUserByUsernameAsync("Ru_Kira");
|
||||
var test = await coreClient.GetUserByUsernameAsync("Test");
|
||||
|
||||
if (keeper is null || kira is null)
|
||||
if (keeper is null || kira is null || test is null)
|
||||
{
|
||||
Console.WriteLine("One or more required users do not exist in RelayCore.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keeper.Licensed || !kira.Licensed)
|
||||
if (!keeper.Licensed || !kira.Licensed || !test.Licensed)
|
||||
{
|
||||
Console.WriteLine("One or more required users are not licensed.");
|
||||
return;
|
||||
@@ -36,11 +35,12 @@ if (!keeper.Licensed || !kira.Licensed)
|
||||
|
||||
Console.WriteLine($"Core verified user: {keeper.Username}");
|
||||
Console.WriteLine($"Core verified user: {kira.Username}");
|
||||
Console.WriteLine($"Core verified user: {test.Username}");
|
||||
|
||||
var server = await db.Create("servers", new Servers
|
||||
{
|
||||
Name = "Test Server",
|
||||
OwnerUserId = kira.Id,
|
||||
OwnerUserId = keeper.Id,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
@@ -60,6 +60,13 @@ var kiraMember = await db.Create("server_members", new ServerMembers
|
||||
IsOwner = false
|
||||
});
|
||||
|
||||
var testMember = await db.Create("server_members", new ServerMembers
|
||||
{
|
||||
UserId = test.Id,
|
||||
JoinedAt = DateTime.UtcNow,
|
||||
IsOwner = false
|
||||
});
|
||||
|
||||
Console.WriteLine("Server members created.");
|
||||
|
||||
var channel = await db.Create("channels", new Channels
|
||||
@@ -72,66 +79,29 @@ Console.WriteLine($"Channel created: {ToJsonString(channel)}");
|
||||
|
||||
var channelId = GetRecordId(channel.Id);
|
||||
Console.WriteLine($"Resolved channelId: {channelId}");
|
||||
ChatTest.DefaultChannelId = channelId;
|
||||
|
||||
var keyBase64 = cryptoService.GenerateKey();
|
||||
var serverKeys = E2EeHelper.GenerateRsaKeyPair();
|
||||
|
||||
var serverKey = await db.Create("server_encryption_keys", new ServerEncryptionKeys
|
||||
{
|
||||
KeyBase64 = keyBase64,
|
||||
PublicKey = serverKeys.publicKey,
|
||||
PrivateKey = serverKeys.privateKey,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
ChatTest.ServerPublicKey = serverKeys.publicKey;
|
||||
ChatTest.ServerPrivateKey = serverKeys.privateKey;
|
||||
ChatTest.ChannelDbKey = keyBase64;
|
||||
|
||||
Console.WriteLine("Server encryption key created.");
|
||||
|
||||
var encrypted = cryptoService.Encrypt("hello from Keeper317 in #general", keyBase64);
|
||||
Console.ReadKey(true);
|
||||
wssv.Stop();
|
||||
|
||||
var savedMessage = await db.Create("channel_messages", new ChannelMessages
|
||||
{
|
||||
ChannelId = channelId,
|
||||
SenderUserId = keeper.Id,
|
||||
CipherText = encrypted.cipherText,
|
||||
Nonce = encrypted.nonce,
|
||||
Tag = encrypted.tag,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
Console.WriteLine($"Encrypted message saved: {ToJsonString(savedMessage)}");
|
||||
|
||||
var decrypted = cryptoService.Decrypt(
|
||||
savedMessage.CipherText,
|
||||
savedMessage.Nonce,
|
||||
savedMessage.Tag,
|
||||
keyBase64
|
||||
);
|
||||
|
||||
var storedMessages = await db.Select<ChannelMessages>("channel_messages");
|
||||
|
||||
Console.WriteLine("Stored DB messages:");
|
||||
Console.WriteLine(ToJsonString(storedMessages));
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Decrypted message: {decrypted}");
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Simulating Kira reading #general...");
|
||||
|
||||
var kiraVisibleMessages = storedMessages
|
||||
.Where(m => m.ChannelId == channelId)
|
||||
.OrderBy(m => m.CreatedAt)
|
||||
.ToList();
|
||||
|
||||
foreach (var msg in kiraVisibleMessages)
|
||||
{
|
||||
var plainText = cryptoService.Decrypt(
|
||||
msg.CipherText,
|
||||
msg.Nonce,
|
||||
msg.Tag,
|
||||
keyBase64
|
||||
);
|
||||
|
||||
Console.WriteLine($"Kira reads message from {msg.SenderUserId}: {plainText}");
|
||||
}
|
||||
return;
|
||||
|
||||
static string ToJsonString(object? obj)
|
||||
@@ -159,14 +129,3 @@ static string GetRecordId(object? id)
|
||||
|
||||
return $"{table}:{recordId}";
|
||||
}
|
||||
|
||||
public class ChatTest : WebSocketBehavior
|
||||
{
|
||||
protected override void OnMessage(MessageEventArgs e)
|
||||
{
|
||||
// var msg = e.Data.Split(":")[1] == "PING" ? "SERVER:PONG" : "SERVER:RESPONSE";
|
||||
var msg = e.Data;
|
||||
Console.WriteLine(msg);
|
||||
Send(msg);
|
||||
}
|
||||
}
|
||||
274
RelayServer/Services/ChatTest.cs
Normal file
274
RelayServer/Services/ChatTest.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using System.Text.Json;
|
||||
using RelayServer.Models;
|
||||
using WebSocketSharp;
|
||||
using WebSocketSharp.Server;
|
||||
|
||||
namespace RelayServer.Services;
|
||||
|
||||
public class ChatTest : WebSocketBehavior
|
||||
{
|
||||
public static ClientKeyService? ClientKeyService { get; set; }
|
||||
public static string? ServerPublicKey { get; set; }
|
||||
public static string? ServerPrivateKey { get; set; }
|
||||
public static string? ChannelDbKey { get; set; }
|
||||
public static SurrealDb.Net.SurrealDbClient? Db { get; set; }
|
||||
public static string? DefaultChannelId { get; set; }
|
||||
|
||||
protected override void OnMessage(MessageEventArgs e)
|
||||
{
|
||||
var msg = e.Data;
|
||||
Console.WriteLine(msg);
|
||||
|
||||
if (msg.StartsWith("REGISTER_KEY|"))
|
||||
{
|
||||
HandleRegisterKey(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg == "GET_SERVER_KEY")
|
||||
{
|
||||
HandleGetServerKey();
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.StartsWith("GET_HISTORY|"))
|
||||
{
|
||||
HandleGetHistory(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
HandleEncryptedClientMessage(msg);
|
||||
}
|
||||
|
||||
private static string ExtractUsernameFromUserId(string senderUserId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(senderUserId))
|
||||
return "Unknown";
|
||||
|
||||
var parts = senderUserId.Split(':', 2);
|
||||
return parts.Length == 2 ? parts[1] : senderUserId;
|
||||
}
|
||||
|
||||
private void HandleRegisterKey(string msg)
|
||||
{
|
||||
var parts = msg.Split('|', 3);
|
||||
|
||||
if (parts.Length < 3)
|
||||
{
|
||||
Console.WriteLine("Invalid REGISTER_KEY payload.");
|
||||
return;
|
||||
}
|
||||
|
||||
var username = parts[1];
|
||||
var publicKey = parts[2];
|
||||
|
||||
if (ClientKeyService is null)
|
||||
{
|
||||
Console.WriteLine("ClientKeyService is not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ClientKeyService.RegisterOrUpdateKeyAsync(username, publicKey);
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
Send($"SERVER:REGISTERED_KEY:{username}");
|
||||
}
|
||||
|
||||
private void HandleGetServerKey()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ServerPublicKey))
|
||||
{
|
||||
Console.WriteLine("Server public key is not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = new ServerPublicKeyMessage
|
||||
{
|
||||
Type = "server_public_key",
|
||||
PublicKey = ServerPublicKey
|
||||
};
|
||||
|
||||
Send(JsonSerializer.Serialize(payload));
|
||||
}
|
||||
|
||||
private void HandleEncryptedClientMessage(string msg)
|
||||
{
|
||||
SocketEncryptedMessage? clientPayload;
|
||||
|
||||
try
|
||||
{
|
||||
clientPayload = JsonSerializer.Deserialize<SocketEncryptedMessage>(msg);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Failed to parse encrypted client payload.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientPayload is null || clientPayload.Type != "client_encrypted_chat")
|
||||
return;
|
||||
|
||||
if (ClientKeyService is null ||
|
||||
Db is null ||
|
||||
string.IsNullOrWhiteSpace(ServerPrivateKey) ||
|
||||
string.IsNullOrWhiteSpace(ChannelDbKey) ||
|
||||
string.IsNullOrWhiteSpace(DefaultChannelId))
|
||||
{
|
||||
Console.WriteLine("Server crypto/database dependencies are not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
string plainText;
|
||||
|
||||
try
|
||||
{
|
||||
plainText = E2EeHelper.DecryptForRecipient(
|
||||
new EncryptedPayload
|
||||
{
|
||||
CipherText = clientPayload.CipherText,
|
||||
Nonce = clientPayload.Nonce,
|
||||
Tag = clientPayload.Tag,
|
||||
EncryptedKey = clientPayload.EncryptedKey
|
||||
},
|
||||
ServerPrivateKey
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to decrypt client payload: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Server decrypted message from {clientPayload.SenderUsername}: {plainText}");
|
||||
try
|
||||
{
|
||||
var channelCrypto = new ChannelCryptoService();
|
||||
var dbEncrypted = channelCrypto.Encrypt(plainText, ChannelDbKey);
|
||||
|
||||
var savedMessage = Task.Run(async () =>
|
||||
await Db.Create("channel_messages", new ChannelMessages
|
||||
{
|
||||
ChannelId = DefaultChannelId,
|
||||
SenderUserId = $"users:{clientPayload.SenderUsername.ToLower()}",
|
||||
CipherText = dbEncrypted.cipherText,
|
||||
Nonce = dbEncrypted.nonce,
|
||||
Tag = dbEncrypted.tag,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
})
|
||||
).GetAwaiter().GetResult();
|
||||
|
||||
Console.WriteLine($"Live message saved to DB: {JsonSerializer.Serialize(savedMessage)}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to save live message to DB: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
var allKeys = Task.Run(async () => await ClientKeyService.GetAllAsync())
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
foreach (var client in allKeys)
|
||||
{
|
||||
var encrypted = E2EeHelper.EncryptForRecipient(plainText, client.PublicKey);
|
||||
|
||||
Console.WriteLine($"Encrypting outbound message from {clientPayload.SenderUsername} for {client.Username}");
|
||||
|
||||
var outbound = new SocketEncryptedMessage
|
||||
{
|
||||
Type = "encrypted_chat",
|
||||
SenderUsername = clientPayload.SenderUsername,
|
||||
RecipientUsername = client.Username,
|
||||
CipherText = encrypted.CipherText,
|
||||
Nonce = encrypted.Nonce,
|
||||
Tag = encrypted.Tag,
|
||||
EncryptedKey = encrypted.EncryptedKey
|
||||
};
|
||||
|
||||
Sessions.Broadcast(JsonSerializer.Serialize(outbound));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGetHistory(string msg)
|
||||
{
|
||||
var parts = msg.Split('|', 2);
|
||||
|
||||
if (parts.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Invalid GET_HISTORY payload.");
|
||||
return;
|
||||
}
|
||||
|
||||
var username = parts[1];
|
||||
|
||||
if (ClientKeyService is null ||
|
||||
Db is null ||
|
||||
string.IsNullOrWhiteSpace(ChannelDbKey) ||
|
||||
string.IsNullOrWhiteSpace(DefaultChannelId))
|
||||
{
|
||||
Console.WriteLine("History dependencies are not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetClient = Task.Run(async () => await ClientKeyService.GetByUsernameAsync(username))
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (targetClient is null)
|
||||
{
|
||||
Console.WriteLine($"No public key found for history request user {username}");
|
||||
return;
|
||||
}
|
||||
|
||||
var allMessages = Task.Run(async () => await Db.Select<ChannelMessages>("channel_messages"))
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
var channelMessages = allMessages
|
||||
.Where(m => m.ChannelId == DefaultChannelId)
|
||||
.OrderBy(m => m.CreatedAt)
|
||||
.ToList();
|
||||
|
||||
Console.WriteLine($"Sending {channelMessages.Count} history messages to {username}");
|
||||
|
||||
var channelCrypto = new ChannelCryptoService();
|
||||
|
||||
foreach (var dbMessage in channelMessages)
|
||||
{
|
||||
string plainText;
|
||||
|
||||
try
|
||||
{
|
||||
plainText = channelCrypto.Decrypt(
|
||||
dbMessage.CipherText,
|
||||
dbMessage.Nonce,
|
||||
dbMessage.Tag,
|
||||
ChannelDbKey
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to decrypt DB history row {dbMessage.Id}: {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var encrypted = E2EeHelper.EncryptForRecipient(plainText, targetClient.PublicKey);
|
||||
|
||||
var outbound = new SocketEncryptedMessage
|
||||
{
|
||||
Type = "encrypted_chat",
|
||||
SenderUsername = ExtractUsernameFromUserId(dbMessage.SenderUserId),
|
||||
RecipientUsername = username,
|
||||
CipherText = encrypted.CipherText,
|
||||
Nonce = encrypted.Nonce,
|
||||
Tag = encrypted.Tag,
|
||||
EncryptedKey = encrypted.EncryptedKey
|
||||
};
|
||||
|
||||
Send(JsonSerializer.Serialize(outbound));
|
||||
}
|
||||
}
|
||||
}
|
||||
61
RelayServer/Services/ClientKeyService.cs
Normal file
61
RelayServer/Services/ClientKeyService.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using RelayServer.Models;
|
||||
using SurrealDb.Net;
|
||||
|
||||
namespace RelayServer.Services;
|
||||
|
||||
public sealed class ClientKeyService
|
||||
{
|
||||
private readonly SurrealDbClient _db;
|
||||
|
||||
public ClientKeyService(SurrealDbClient db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task RegisterOrUpdateKeyAsync(string username, string publicKey)
|
||||
{
|
||||
var allKeys = await _db.Select<ClientPublicKeys>("client_public_keys");
|
||||
|
||||
var existing = allKeys.FirstOrDefault(x => x.Username == username);
|
||||
|
||||
if (existing is null)
|
||||
{
|
||||
await _db.Create("client_public_keys", new ClientPublicKeys
|
||||
{
|
||||
Username = username,
|
||||
PublicKey = publicKey,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
Console.WriteLine($"Stored public key for {username}");
|
||||
return;
|
||||
}
|
||||
|
||||
existing.PublicKey = publicKey;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _db.Merge<ClientPublicKeys, ClientPublicKeys>(new ClientPublicKeys
|
||||
{
|
||||
Id = existing.Id,
|
||||
Username = existing.Username,
|
||||
PublicKey = existing.PublicKey,
|
||||
CreatedAt = existing.CreatedAt,
|
||||
UpdatedAt = existing.UpdatedAt
|
||||
});
|
||||
|
||||
Console.WriteLine($"Updated public key for {username}");
|
||||
}
|
||||
|
||||
public async Task<ClientPublicKeys?> GetByUsernameAsync(string username)
|
||||
{
|
||||
var allKeys = await _db.Select<ClientPublicKeys>("client_public_keys");
|
||||
return allKeys.FirstOrDefault(x => x.Username == username);
|
||||
}
|
||||
|
||||
public async Task<List<ClientPublicKeys>> GetAllAsync()
|
||||
{
|
||||
var allKeys = await _db.Select<ClientPublicKeys>("client_public_keys");
|
||||
return allKeys.ToList();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public sealed class CoreClientService
|
||||
{
|
||||
"Keeper317" => new CoreUser("users:keeper317", "Keeper317", true),
|
||||
"Ru_Kira" => new CoreUser("users:ru_kira", "Ru_Kira", true),
|
||||
"Test" => new CoreUser("users:test", "Test", true),
|
||||
_ => null
|
||||
});
|
||||
}
|
||||
|
||||
78
RelayServer/Services/E2EeHelper.cs
Normal file
78
RelayServer/Services/E2EeHelper.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace RelayServer.Services;
|
||||
|
||||
public static class E2EeHelper
|
||||
{
|
||||
public static (string publicKey, string privateKey) GenerateRsaKeyPair()
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
|
||||
return (
|
||||
Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()),
|
||||
Convert.ToBase64String(rsa.ExportPkcs8PrivateKey())
|
||||
);
|
||||
}
|
||||
|
||||
public static EncryptedPayload EncryptForRecipient(string plainText, string recipientPublicKeyBase64)
|
||||
{
|
||||
byte[] aesKey = RandomNumberGenerator.GetBytes(32);
|
||||
byte[] nonce = RandomNumberGenerator.GetBytes(12);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = new byte[plainBytes.Length];
|
||||
byte[] tag = new byte[16];
|
||||
|
||||
using (var aes = new AesGcm(aesKey, 16))
|
||||
{
|
||||
aes.Encrypt(nonce, plainBytes, cipherBytes, tag);
|
||||
}
|
||||
|
||||
byte[] encryptedKey;
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(recipientPublicKeyBase64), out _);
|
||||
encryptedKey = rsa.Encrypt(aesKey, RSAEncryptionPadding.OaepSHA256);
|
||||
}
|
||||
|
||||
return new EncryptedPayload
|
||||
{
|
||||
CipherText = Convert.ToBase64String(cipherBytes),
|
||||
Nonce = Convert.ToBase64String(nonce),
|
||||
Tag = Convert.ToBase64String(tag),
|
||||
EncryptedKey = Convert.ToBase64String(encryptedKey)
|
||||
};
|
||||
}
|
||||
|
||||
public static string DecryptForRecipient(EncryptedPayload payload, string recipientPrivateKeyBase64)
|
||||
{
|
||||
byte[] aesKey;
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(recipientPrivateKeyBase64), out _);
|
||||
aesKey = rsa.Decrypt(Convert.FromBase64String(payload.EncryptedKey), RSAEncryptionPadding.OaepSHA256);
|
||||
}
|
||||
|
||||
byte[] plainBytes = new byte[Convert.FromBase64String(payload.CipherText).Length];
|
||||
|
||||
using (var aes = new AesGcm(aesKey, 16))
|
||||
{
|
||||
aes.Decrypt(
|
||||
Convert.FromBase64String(payload.Nonce),
|
||||
Convert.FromBase64String(payload.CipherText),
|
||||
Convert.FromBase64String(payload.Tag),
|
||||
plainBytes
|
||||
);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public class EncryptedPayload
|
||||
{
|
||||
public required string CipherText { get; set; }
|
||||
public required string Nonce { get; set; }
|
||||
public required string Tag { get; set; }
|
||||
public required string EncryptedKey { get; set; }
|
||||
}
|
||||
123
dev-run.ps1
Normal file
123
dev-run.ps1
Normal file
@@ -0,0 +1,123 @@
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $root
|
||||
|
||||
$startedProcesses = @()
|
||||
|
||||
function Start-TrackedProcess {
|
||||
param(
|
||||
[string]$FilePath,
|
||||
[string[]]$ArgumentList = @(),
|
||||
[string]$WorkingDirectory = $root
|
||||
)
|
||||
|
||||
$proc = Start-Process -FilePath $FilePath `
|
||||
-ArgumentList $ArgumentList `
|
||||
-WorkingDirectory $WorkingDirectory `
|
||||
-PassThru
|
||||
|
||||
$script:startedProcesses += $proc
|
||||
return $proc
|
||||
}
|
||||
|
||||
function Stop-AllTrackedProcesses {
|
||||
Write-Host ""
|
||||
Write-Host "Stopping launched processes..."
|
||||
|
||||
foreach ($proc in $script:startedProcesses) {
|
||||
try {
|
||||
if ($proc -and -not $proc.HasExited) {
|
||||
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "Stopped PID $($proc.Id)"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$containerIds = docker ps --filter "ancestor=surrealdb/surrealdb:v2.2.1" --format "{{.ID}}"
|
||||
foreach ($id in $containerIds) {
|
||||
docker stop $id | Out-Null
|
||||
Write-Host "Stopped Docker container $id"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
}
|
||||
|
||||
Register-EngineEvent PowerShell.Exiting -Action {
|
||||
Stop-AllTrackedProcesses
|
||||
} | Out-Null
|
||||
|
||||
Write-Host "Building RelayCore..."
|
||||
dotnet build .\RelayCore\RelayCore.csproj
|
||||
if ($LASTEXITCODE -ne 0) { throw "RelayCore build failed." }
|
||||
|
||||
Write-Host "Building RelayServer..."
|
||||
dotnet build .\RelayServer\RelayServer.csproj
|
||||
if ($LASTEXITCODE -ne 0) { throw "RelayServer build failed." }
|
||||
|
||||
Write-Host "Building RelayClient (Windows only)..."
|
||||
dotnet build .\RelayClient\RelayClient.csproj -f net10.0-windows10.0.19041.0
|
||||
if ($LASTEXITCODE -ne 0) { throw "RelayClient build failed." }
|
||||
|
||||
$coreDll = Join-Path $root "RelayCore\bin\Debug\net9.0\RelayCore.dll"
|
||||
$serverDll = Join-Path $root "RelayServer\bin\Debug\net10.0\RelayServer.dll"
|
||||
$clientExe = Join-Path $root "RelayClient\bin\Debug\net10.0-windows10.0.19041.0\win-x64\RelayClient.exe"
|
||||
|
||||
Write-Host "Starting SurrealDB..."
|
||||
Start-TrackedProcess `
|
||||
-FilePath "docker" `
|
||||
-ArgumentList @(
|
||||
"run",
|
||||
"--rm",
|
||||
"-p", "8000:8000",
|
||||
"-v", "/mydata:/mydata",
|
||||
"surrealdb/surrealdb:v2.2.1",
|
||||
"start",
|
||||
"--user", "root",
|
||||
"--pass", "secret"
|
||||
)
|
||||
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
Write-Host "Starting RelayCore..."
|
||||
Start-TrackedProcess `
|
||||
-FilePath "dotnet" `
|
||||
-ArgumentList @($coreDll)
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
Write-Host "Starting RelayServer..."
|
||||
Start-TrackedProcess `
|
||||
-FilePath "dotnet" `
|
||||
-ArgumentList @($serverDll)
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
Write-Host "Starting RelayClient (Keeper317)..."
|
||||
Start-TrackedProcess `
|
||||
-FilePath $clientExe `
|
||||
-ArgumentList @("--user", "Keeper317")
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
|
||||
Write-Host "Starting RelayClient (Ru_Kira)..."
|
||||
Start-TrackedProcess `
|
||||
-FilePath $clientExe `
|
||||
-ArgumentList @("--user", "Ru_Kira")
|
||||
|
||||
Start-Sleep -Seconds 20
|
||||
|
||||
Write-Host "Starting RelayClient (Test)..."
|
||||
Start-TrackedProcess `
|
||||
-FilePath $clientExe `
|
||||
-ArgumentList @("--user", "Test")
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Everything started."
|
||||
Write-Host "Press Ctrl+C in this PowerShell to stop everything."
|
||||
|
||||
while ($true) {
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
85
start-all.ps1
Normal file
85
start-all.ps1
Normal file
@@ -0,0 +1,85 @@
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $root
|
||||
|
||||
$dockerExe = (Get-Command docker.exe).Source
|
||||
$dotnetExe = (Get-Command dotnet.exe).Source
|
||||
$ps = (Get-Command powershell.exe).Source
|
||||
|
||||
Write-Host "Building RelayCore..."
|
||||
& $dotnetExe build .\RelayCore\RelayCore.csproj
|
||||
if ($LASTEXITCODE -ne 0) { throw "RelayCore build failed." }
|
||||
|
||||
Write-Host "Building RelayServer..."
|
||||
& $dotnetExe build .\RelayServer\RelayServer.csproj
|
||||
if ($LASTEXITCODE -ne 0) { throw "RelayServer build failed." }
|
||||
|
||||
Write-Host "Building RelayClient (Windows only)..."
|
||||
& $dotnetExe build .\RelayClient\RelayClient.csproj -f net10.0-windows10.0.19041.0
|
||||
if ($LASTEXITCODE -ne 0) { throw "RelayClient build failed." }
|
||||
|
||||
$coreDll = Join-Path $root "RelayCore\bin\Debug\net9.0\RelayCore.dll"
|
||||
$serverDll = Join-Path $root "RelayServer\bin\Debug\net10.0\RelayServer.dll"
|
||||
$clientExe = Join-Path $root "RelayClient\bin\Debug\net10.0-windows10.0.19041.0\win-x64\RelayClient.exe"
|
||||
|
||||
$tempDir = Join-Path $env:TEMP "RelayTabs"
|
||||
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
||||
|
||||
function New-TabScript {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Content
|
||||
)
|
||||
|
||||
$path = Join-Path $tempDir "$Name.ps1"
|
||||
Set-Content -Path $path -Value $Content -Encoding UTF8
|
||||
return $path
|
||||
}
|
||||
|
||||
$dockerScript = New-TabScript -Name "SurrealDB" -Content @"
|
||||
Set-Location '$root'
|
||||
& '$dockerExe' run --rm -p 8000:8000 -v /mydata:/mydata surrealdb/surrealdb:v2.2.1 start --user root --pass secret
|
||||
"@
|
||||
|
||||
$coreScript = New-TabScript -Name "RelayCore" -Content @"
|
||||
Set-Location '$root'
|
||||
Start-Sleep -Seconds 1
|
||||
& '$dotnetExe' '$coreDll'
|
||||
"@
|
||||
|
||||
$serverScript = New-TabScript -Name "RelayServer" -Content @"
|
||||
Set-Location '$root'
|
||||
Start-Sleep -Seconds 1
|
||||
& '$dotnetExe' '$serverDll'
|
||||
"@
|
||||
|
||||
$keeperScript = New-TabScript -Name "Keeper317" -Content @"
|
||||
Set-Location '$root'
|
||||
Start-Sleep -Seconds 5
|
||||
& '$clientExe' --user Keeper317
|
||||
"@
|
||||
|
||||
$kiraScript = New-TabScript -Name "Ru_Kira" -Content @"
|
||||
Set-Location '$root'
|
||||
Start-Sleep -Seconds 5
|
||||
& '$clientExe' --user Ru_Kira
|
||||
"@
|
||||
|
||||
$testScript = New-TabScript -Name "Test" -Content @"
|
||||
Set-Location '$root'
|
||||
Start-Sleep -Seconds 25
|
||||
& '$clientExe' --user Test
|
||||
"@
|
||||
|
||||
$wtArgs = @(
|
||||
"new-tab --title `"SurrealDB`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$dockerScript`"",
|
||||
"new-tab --title `"RelayCore`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$coreScript`"",
|
||||
"new-tab --title `"RelayServer`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$serverScript`"",
|
||||
"new-tab --title `"Keeper317`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$keeperScript`"",
|
||||
"new-tab --title `"Ru_Kira`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$kiraScript`"",
|
||||
"new-tab --title `"Test`" `"$ps`" -NoExit -ExecutionPolicy Bypass -File `"$testScript`""
|
||||
) -join " ; "
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Everything started."
|
||||
Write-Host "Close out terminal to end all applications."
|
||||
Start-Process wt.exe -ArgumentList $wtArgs
|
||||
Reference in New Issue
Block a user