mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-06 07:36:01 +02:00
feat: Implement end-to-end encryption for channels
- Added EncryptionSalt and WrappedRoomKey properties to Channel model. - Introduced RoomCrypto class for client-side encryption and decryption. - Updated ChannelService to handle encrypted channels, including creation and rekeying. - Modified ChannelsController to expose crypto metadata and rekey functionality. - Enhanced IrcCommandHandler to block joining encrypted channels over IRC. - Updated database schema with migration for new encryption fields. - Refactored file validation and image processing services to accommodate encrypted channels. - Added unit tests for RoomCrypto functionality and updated existing tests for channel services.
This commit is contained in:
@@ -185,7 +185,8 @@ public sealed class ApiClient : IDisposable
|
||||
return result?.AvatarAscii;
|
||||
}
|
||||
|
||||
public async Task<MessageDto?> UploadFileAsync(string channelName, Stream fileStream, string fileName, string? size = null)
|
||||
public async Task<MessageDto?> UploadFileAsync(string channelName, Stream fileStream, string fileName, string? size = null,
|
||||
string? declaredType = null, string? encryptedContent = null)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
using var content = new MultipartFormDataContent();
|
||||
@@ -193,6 +194,13 @@ public sealed class ApiClient : IDisposable
|
||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue(GetContentType(fileName));
|
||||
content.Add(streamContent, "file", fileName);
|
||||
|
||||
// E2E channels: the blob is ciphertext, so the client declares the type and
|
||||
// supplies the room-encrypted message content the server can't produce.
|
||||
if (declaredType is not null)
|
||||
content.Add(new StringContent(declaredType), "type");
|
||||
if (encryptedContent is not null)
|
||||
content.Add(new StringContent(encryptedContent), "content");
|
||||
|
||||
var sizeQuery = size is not null ? $"?size={size}" : "";
|
||||
using var response = await AuthenticatedRequestAsync(() =>
|
||||
_http.PostAsync($"/api/channels/{Uri.EscapeDataString(channelName)}/upload{sizeQuery}", content));
|
||||
@@ -228,16 +236,40 @@ public sealed class ApiClient : IDisposable
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public async Task<ChannelDto?> CreateChannelAsync(string name, string? topic = null, bool isPublic = true, string? password = null)
|
||||
public async Task<ChannelDto?> CreateChannelAsync(string name, string? topic = null, bool isPublic = true,
|
||||
string? password = null, string? encryptionSalt = null, string? wrappedRoomKey = null)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
var request = new CreateChannelRequest(name, topic, isPublic, password);
|
||||
var request = new CreateChannelRequest(name, topic, isPublic, password, encryptionSalt, wrappedRoomKey);
|
||||
using var response = await AuthenticatedRequestAsync(() =>
|
||||
_http.PostAsJsonAsync("/api/channels", request));
|
||||
await EnsureSuccessAsync(response);
|
||||
return await response.Content.ReadFromJsonAsync<ChannelDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a channel's public crypto metadata (whether it's E2E-encrypted and its
|
||||
/// key-derivation salt). Returns null when the channel doesn't exist.
|
||||
/// </summary>
|
||||
public async Task<ChannelCryptoDto?> GetChannelCryptoAsync(string channelName)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
using var response = await AuthenticatedGetAsync($"/api/channels/{Uri.EscapeDataString(channelName)}/crypto");
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
return null;
|
||||
await EnsureSuccessAsync(response);
|
||||
return await response.Content.ReadFromJsonAsync<ChannelCryptoDto>();
|
||||
}
|
||||
|
||||
public async Task<ChannelDto?> RekeyChannelAsync(string channelName, RekeyChannelRequest request)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
using var response = await AuthenticatedRequestAsync(() =>
|
||||
_http.PostAsJsonAsync($"/api/channels/{Uri.EscapeDataString(channelName)}/rekey", request));
|
||||
await EnsureSuccessAsync(response);
|
||||
return await response.Content.ReadFromJsonAsync<ChannelDto>();
|
||||
}
|
||||
|
||||
public async Task<ChannelDto?> UpdateChannelTopicAsync(string channelName, string? topic)
|
||||
{
|
||||
EnsureAuthenticated();
|
||||
|
||||
Reference in New Issue
Block a user