204 lines
6.2 KiB
C#
204 lines
6.2 KiB
C#
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)
|
|
{
|
|
InitializeComponent();
|
|
|
|
_username = username;
|
|
UserLabel.Text = $"Logged in as: {_username}";
|
|
|
|
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();
|
|
}
|
|
|
|
private void MessageEntry_OnCompleted(object? sender, EventArgs e)
|
|
{
|
|
SendMessage();
|
|
}
|
|
|
|
private void SendMessage()
|
|
{
|
|
var text = MessageEntry.Text?.Trim();
|
|
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
return;
|
|
|
|
if (string.IsNullOrWhiteSpace(_serverPublicKey))
|
|
{
|
|
Console.WriteLine("Server public key not loaded yet.");
|
|
return;
|
|
}
|
|
|
|
var encrypted = E2EeHelper.EncryptForRecipient(text, _serverPublicKey);
|
|
|
|
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 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;
|
|
|
|
var bubble = new Border
|
|
{
|
|
StrokeThickness = 1,
|
|
Padding = 10,
|
|
Margin = isOwnMessage
|
|
? new Thickness(40, 0, 0, 0)
|
|
: new Thickness(0, 0, 40, 0),
|
|
HorizontalOptions = isOwnMessage
|
|
? LayoutOptions.End
|
|
: LayoutOptions.Start,
|
|
Content = new VerticalStackLayout
|
|
{
|
|
Spacing = 2,
|
|
Children =
|
|
{
|
|
new Label
|
|
{
|
|
Text = message.SenderUsername,
|
|
FontAttributes = FontAttributes.Bold,
|
|
FontSize = 12
|
|
},
|
|
new Label
|
|
{
|
|
Text = message.Text,
|
|
FontSize = 14
|
|
},
|
|
new Label
|
|
{
|
|
Text = message.Timestamp.ToString("h:mm tt"),
|
|
FontSize = 10
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
MessagesLayout.Children.Add(bubble);
|
|
await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true);
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{_username}] failed to process websocket message: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
protected override void OnDisappearing()
|
|
{
|
|
wsc.OnMessage -= WscOnMessage;
|
|
wsc.Close();
|
|
base.OnDisappearing();
|
|
}
|
|
} |