mirror of
https://github.com/Stone-Red-Code/Decho.git
synced 2026-09-04 00:46:11 +02:00
Add end-to-end encryption support for channel attachments
This commit is contained in:
@@ -7,6 +7,7 @@ using EchoHub.Core.Constants;
|
|||||||
using EchoHub.Core.DTOs;
|
using EchoHub.Core.DTOs;
|
||||||
using EchoHub.Core.Models;
|
using EchoHub.Core.Models;
|
||||||
using EchoHub.Core.Security;
|
using EchoHub.Core.Security;
|
||||||
|
using EchoHub.Core.Services;
|
||||||
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
@@ -309,9 +310,41 @@ public sealed class ConnectionService : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using FileStream stream = File.OpenRead(filePath);
|
|
||||||
string fileName = Path.GetFileName(filePath);
|
string fileName = Path.GetFileName(filePath);
|
||||||
OutgoingAttachment attachment = new OutgoingAttachment(stream, fileName);
|
OutgoingAttachment attachment;
|
||||||
|
|
||||||
|
if (entry.Manager.RoomKeys.TryGetKey(channelName, out byte[]? roomKey))
|
||||||
|
{
|
||||||
|
// End-to-end encrypted channel: encrypt the blob locally, declare its kind,
|
||||||
|
// and room-encrypt an image ASCII preview — the server stores the ciphertext as-is.
|
||||||
|
byte[] bytes = await File.ReadAllBytesAsync(filePath);
|
||||||
|
string declaredKind;
|
||||||
|
string? preview = null;
|
||||||
|
|
||||||
|
await using (var ms = new MemoryStream(bytes))
|
||||||
|
{
|
||||||
|
if (FileValidationHelper.IsValidImage(ms))
|
||||||
|
{
|
||||||
|
declaredKind = "image";
|
||||||
|
var (w, h) = ImageToAsciiService.GetDimensions(size);
|
||||||
|
ms.Position = 0;
|
||||||
|
preview = RoomCrypto.EncryptText(new ImageToAsciiService().ConvertToAscii(ms, w, h), roomKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
declaredKind = FileValidationHelper.IsAudioFile(fileName) ? "audio" : "file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encryptedBlob = RoomCrypto.EncryptBytes(bytes, roomKey);
|
||||||
|
attachment = new OutgoingAttachment(new MemoryStream(encryptedBlob), fileName, declaredKind, preview);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await using FileStream stream = File.OpenRead(filePath);
|
||||||
|
attachment = new OutgoingAttachment(stream, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
_ = await entry.ApiClient.SendMessageWithAttachmentsAsync(channelName, "", [attachment], size);
|
_ = await entry.ApiClient.SendMessageWithAttachmentsAsync(channelName, "", [attachment], size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +395,7 @@ public sealed class ConnectionService : IDisposable
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> DownloadAttachmentAsync(string serverUrl, string relativeUrl, string fileName)
|
public async Task<string?> DownloadAttachmentAsync(string serverUrl, string channelName, string relativeUrl, string fileName)
|
||||||
{
|
{
|
||||||
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
||||||
{
|
{
|
||||||
@@ -371,7 +404,26 @@ public sealed class ConnectionService : IDisposable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await entry.ApiClient.DownloadFileToTempAsync(relativeUrl, fileName);
|
string? tempPath = await entry.ApiClient.DownloadFileToTempAsync(relativeUrl, fileName);
|
||||||
|
if (tempPath is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Manager.RoomKeys.TryGetKey(channelName, out byte[]? roomKey))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] encrypted = await File.ReadAllBytesAsync(tempPath);
|
||||||
|
await File.WriteAllBytesAsync(tempPath, RoomCrypto.DecryptBytes(encrypted, roomKey));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Not room ciphertext — leave the downloaded bytes as-is.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempPath;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -379,7 +431,7 @@ public sealed class ConnectionService : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]?> DownloadImageBytesAsync(string serverUrl, string relativeUrl)
|
public async Task<byte[]?> DownloadImageBytesAsync(string serverUrl, string channelName, string relativeUrl)
|
||||||
{
|
{
|
||||||
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
||||||
{
|
{
|
||||||
@@ -403,6 +455,19 @@ public sealed class ConnectionService : IDisposable
|
|||||||
{
|
{
|
||||||
// Ignore if deletion fails
|
// Ignore if deletion fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entry.Manager.RoomKeys.TryGetKey(channelName, out byte[]? roomKey))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytes = RoomCrypto.DecryptBytes(bytes, roomKey);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Not room ciphertext — leave the downloaded bytes as-is.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ public partial class MessageItemView : UserControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte[]? bytes = await mainVm.ConnectionService.DownloadImageBytesAsync(
|
byte[]? bytes = await mainVm.ConnectionService.DownloadImageBytesAsync(
|
||||||
msg.ServerUrl ?? "", att.Url);
|
msg.ServerUrl ?? "", msg.Model.ChannelName, att.Url);
|
||||||
|
|
||||||
cts.Token.ThrowIfCancellationRequested();
|
cts.Token.ThrowIfCancellationRequested();
|
||||||
if (bytes is null || bytes.Length == 0)
|
if (bytes is null || bytes.Length == 0)
|
||||||
@@ -294,7 +294,8 @@ public partial class MessageItemView : UserControl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? tempPath = await mainVm.ConnectionService.DownloadAttachmentAsync(msg.ServerUrl ?? "", att.Url, att.FileName);
|
string? tempPath = await mainVm.ConnectionService.DownloadAttachmentAsync(
|
||||||
|
msg.ServerUrl ?? "", msg.Model.ChannelName, att.Url, att.FileName);
|
||||||
|
|
||||||
if (tempPath is null)
|
if (tempPath is null)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user