add encryption

This commit is contained in:
marlon-erler
2024-07-21 03:17:40 +02:00
parent c45f14c240
commit ee50d88066
7 changed files with 115 additions and 17 deletions
+3
View File
@@ -438,6 +438,9 @@ body[showing-chat] #chat-page-wrapper {
.message-bubble .sender-name { .message-bubble .sender-name {
font-weight: bold; font-weight: bold;
} }
.message-bubble .body {
word-break: break-all;
}
.message-bubble[sentbyuser] { .message-bubble[sentbyuser] {
justify-content: flex-end; justify-content: flex-end;
} }
+89 -9
View File
@@ -205,9 +205,7 @@
try { try {
const [listState, toElement] = value; const [listState, toElement] = value;
listState.handleAddition((newItem) => { listState.handleAddition((newItem) => {
console.log(newItem, toElement);
const child = toElement(newItem); const child = toElement(newItem);
console.log(child);
listState.handleRemoval( listState.handleRemoval(
newItem, newItem,
() => child.remove() () => child.remove()
@@ -372,6 +370,79 @@
chatObjects: (id) => [DATA_VERSION, "chat", id, "objects"] chatObjects: (id) => [DATA_VERSION, "chat", id, "objects"]
}; };
// src/Model/Utility/crypto.ts
var IV_SIZE = 12;
var ENCRYPTION_ALG = "AES-GCM";
async function encryptString(plaintext, passphrase) {
if (!window.crypto.subtle) return plaintext;
const iv = generateIV();
const key = await importKey(passphrase, "encrypt");
const encryptedArray = await encrypt(iv, key, plaintext);
const encryptionData = {
iv: uInt8ToArray(iv),
encryptedArray: uInt8ToArray(encryptedArray)
};
return btoa(JSON.stringify(encryptionData));
}
async function decryptString(cyphertext, passphrase) {
try {
const encrypionData = JSON.parse(atob(cyphertext));
const iv = arrayToUint8(encrypionData.iv);
const encryptedArray = arrayToUint8(encrypionData.encryptedArray);
const key = await importKey(passphrase, "decrypt");
return await decrypt(iv, key, encryptedArray);
} catch {
return cyphertext;
}
}
function encode(string) {
return new TextEncoder().encode(string);
}
function decode(array) {
return new TextDecoder("utf-8").decode(array);
}
async function encrypt(iv, key, message) {
const arrayBuffer = await window.crypto.subtle.encrypt(
{ name: ENCRYPTION_ALG, iv },
key,
encode(message)
);
return new Uint8Array(arrayBuffer);
}
async function decrypt(iv, key, cyphertext) {
const arrayBuffer = await crypto.subtle.decrypt(
{ name: ENCRYPTION_ALG, iv },
key,
cyphertext
);
return arrayBufferToString(arrayBuffer);
}
async function hash(encoded) {
return await crypto.subtle.digest("SHA-256", encoded);
}
function generateIV() {
return crypto.getRandomValues(new Uint8Array(IV_SIZE));
}
async function importKey(passphrase, purpose) {
return await crypto.subtle.importKey(
"raw",
await hash(encode(passphrase)),
{ name: ENCRYPTION_ALG },
false,
[purpose]
);
}
function arrayBufferToString(arrayBuffer) {
const uInt8Array = new Uint8Array(arrayBuffer);
return decode(uInt8Array);
}
function uInt8ToArray(uInt8Array) {
return Array.from(uInt8Array);
}
function arrayToUint8(array) {
return new Uint8Array(array);
}
// src/ViewModel/colors.ts // src/ViewModel/colors.ts
var Color = /* @__PURE__ */ ((Color2) => { var Color = /* @__PURE__ */ ((Color2) => {
Color2["Standard"] = "standard"; Color2["Standard"] = "standard";
@@ -509,7 +580,7 @@
return objects; return objects;
} }
// messaging // messaging
sendMessage = (body) => { sendMessage = async (body) => {
const senderName = this.settingsModel.username; const senderName = this.settingsModel.username;
if (senderName == "") return false; if (senderName == "") return false;
const allChannels = [this.info.primaryChannel]; const allChannels = [this.info.primaryChannel];
@@ -517,6 +588,9 @@
allChannels.push(secondaryChannel); allChannels.push(secondaryChannel);
} }
const combinedChannel = allChannels.join("/"); const combinedChannel = allChannels.join("/");
if (this.info.encryptionKey != "") {
body = await encryptString(body, this.info.encryptionKey);
}
const chatMessage = _ChatModel.createChatMessage( const chatMessage = _ChatModel.createChatMessage(
combinedChannel, combinedChannel,
senderName, senderName,
@@ -525,11 +599,19 @@
this.connectionModel.sendMessageOrStore(chatMessage); this.connectionModel.sendMessageOrStore(chatMessage);
return true; return true;
}; };
handleMessage = (channel, body) => { handleMessage = async (body) => {
const chatMessage = _ChatModel.parseMessage(body); const chatMessage = _ChatModel.parseMessage(body);
if (chatMessage == null) return; if (chatMessage == null) return;
await this.decryptMessage(chatMessage);
this.addMessage(chatMessage); this.addMessage(chatMessage);
}; };
decryptMessage = async (chatMessage) => {
const decryptedBody = await decryptString(
chatMessage.body,
this.info.encryptionKey
);
chatMessage.body = decryptedBody;
};
setMessageHandler = (handler) => { setMessageHandler = (handler) => {
this.chatMessageHandler = handler; this.chatMessageHandler = handler;
}; };
@@ -595,7 +677,7 @@
const allChannels = channel.split("/"); const allChannels = channel.split("/");
for (const channel2 of allChannels) { for (const channel2 of allChannels) {
if (channel2 != chatModel.info.primaryChannel) continue; if (channel2 != chatModel.info.primaryChannel) continue;
chatModel.handleMessage(channel2, body); chatModel.handleMessage(body);
break; break;
} }
} }
@@ -916,17 +998,16 @@
class: "message-bubble", class: "message-bubble",
"toggle:sentbyuser": chatMessageViewModel.sentByUser "toggle:sentbyuser": chatMessageViewModel.sentByUser
}, },
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement( /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
"span", "span",
{ {
class: "body", class: "body",
"subscribe:innerText": chatMessageViewModel.body "subscribe:innerText": chatMessageViewModel.body
} }
), /* @__PURE__ */ createElement("span", { class: "timestamp" }, chatMessageViewModel.dateSent)) ), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, chatMessageViewModel.dateSent))
); );
} }
var ChatMessageViewModelToView = (chatMessageViewModel) => { var ChatMessageViewModelToView = (chatMessageViewModel) => {
console.log(chatMessageViewModel);
return ChatMessage(chatMessageViewModel); return ChatMessage(chatMessageViewModel);
}; };
@@ -1030,7 +1111,6 @@
} }
); );
function scrollDown() { function scrollDown() {
console.log("down");
messageContainer.scrollTop = messageContainer.scrollHeight; messageContainer.scrollTop = messageContainer.scrollHeight;
} }
function scrollDownIfApplicable() { function scrollDownIfApplicable() {
+1 -1
View File
@@ -27,7 +27,7 @@ export default class ChatListModel {
const allChannels = channel.split("/"); const allChannels = channel.split("/");
for (const channel of allChannels) { for (const channel of allChannels) {
if (channel != chatModel.info.primaryChannel) continue; if (channel != chatModel.info.primaryChannel) continue;
chatModel.handleMessage(channel, body); chatModel.handleMessage(body);
break; break;
} }
} }
+16 -3
View File
@@ -2,6 +2,7 @@
import StorageModel, { storageKeys } from "./storageModel"; import StorageModel, { storageKeys } from "./storageModel";
import { createTimestamp, parse } from "./Utility/utility"; import { createTimestamp, parse } from "./Utility/utility";
import { decryptString, encryptString } from "./Utility/crypto";
import ChatListModel from "./chatListModel"; import ChatListModel from "./chatListModel";
import { Color } from "../ViewModel/colors"; import { Color } from "../ViewModel/colors";
@@ -166,7 +167,7 @@ export class ChatModel {
} }
// messaging // messaging
sendMessage = (body: string): boolean => { sendMessage = async (body: string): Promise<boolean> => {
const senderName = this.settingsModel.username; const senderName = this.settingsModel.username;
if (senderName == "") return false; if (senderName == "") return false;
@@ -175,7 +176,10 @@ export class ChatModel {
allChannels.push(secondaryChannel); allChannels.push(secondaryChannel);
} }
const combinedChannel = allChannels.join("/"); const combinedChannel: string = allChannels.join("/");
if (this.info.encryptionKey != "") {
body = await encryptString(body, this.info.encryptionKey);
}
const chatMessage: ChatMessage = ChatModel.createChatMessage( const chatMessage: ChatMessage = ChatModel.createChatMessage(
combinedChannel, combinedChannel,
@@ -187,13 +191,22 @@ export class ChatModel {
return true; return true;
}; };
handleMessage = (channel: string, body: string): void => { handleMessage = async (body: string): Promise<void> => {
const chatMessage: ChatMessage | null = ChatModel.parseMessage(body); const chatMessage: ChatMessage | null = ChatModel.parseMessage(body);
if (chatMessage == null) return; if (chatMessage == null) return;
await this.decryptMessage(chatMessage);
this.addMessage(chatMessage); this.addMessage(chatMessage);
}; };
decryptMessage = async (chatMessage: ChatMessage): Promise<void> => {
const decryptedBody: string = await decryptString(
chatMessage.body,
this.info.encryptionKey
);
chatMessage.body = decryptedBody;
};
setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => { setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => {
this.chatMessageHandler = handler; this.chatMessageHandler = handler;
}; };
+4
View File
@@ -100,6 +100,10 @@
font-weight: bold; font-weight: bold;
} }
.message-bubble .body {
word-break: break-all;
}
.message-bubble[sentbyuser] { .message-bubble[sentbyuser] {
justify-content: flex-end; justify-content: flex-end;
} }
-1
View File
@@ -18,7 +18,6 @@ export function MessagePage(chatViewModel: ChatViewModel) {
); );
function scrollDown() { function scrollDown() {
console.log("down");
messageContainer.scrollTop = messageContainer.scrollHeight; messageContainer.scrollTop = messageContainer.scrollHeight;
} }
function scrollDownIfApplicable() { function scrollDownIfApplicable() {
+2 -3
View File
@@ -9,12 +9,12 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
toggle:sentbyuser={chatMessageViewModel.sentByUser} toggle:sentbyuser={chatMessageViewModel.sentByUser}
> >
<div> <div>
<span class="sender-name">{chatMessageViewModel.sender}</span> <span class="sender-name ellipsis">{chatMessageViewModel.sender}</span>
<span <span
class="body" class="body"
subscribe:innerText={chatMessageViewModel.body} subscribe:innerText={chatMessageViewModel.body}
></span> ></span>
<span class="timestamp">{chatMessageViewModel.dateSent}</span> <span class="timestamp ellipsis">{chatMessageViewModel.dateSent}</span>
</div> </div>
</div> </div>
); );
@@ -23,6 +23,5 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
export const ChatMessageViewModelToView: React.StateItemConverter< export const ChatMessageViewModelToView: React.StateItemConverter<
ChatMessageViewModel ChatMessageViewModel
> = (chatMessageViewModel: ChatMessageViewModel) => { > = (chatMessageViewModel: ChatMessageViewModel) => {
console.log(chatMessageViewModel);
return ChatMessage(chatMessageViewModel); return ChatMessage(chatMessageViewModel);
}; };