mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 23:38:32 +00:00
Removed node-forge, encryptcord is now fully self contained
This commit is contained in:
parent
3275c01a8b
commit
92287026da
4 changed files with 163 additions and 73 deletions
|
@ -39,7 +39,6 @@
|
|||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"monaco-editor": "^0.43.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"node-forge": "^1.3.1",
|
||||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
@ -37,9 +37,6 @@ dependencies:
|
|||
nanoid:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
node-forge:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
virtual-merge:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
|
@ -2483,11 +2480,6 @@ packages:
|
|||
whatwg-url: 5.0.0
|
||||
dev: true
|
||||
|
||||
/node-forge@1.3.1:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
dev: false
|
||||
|
||||
/normalize-package-data@2.5.0:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
dependencies:
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
||||
import { removeButton } from "@api/MessagePopover";
|
||||
import definePlugin, { StartAt } from "@utils/types";
|
||||
import { addDecoration } from "@api/MessageDecorations";
|
||||
import definePlugin from "@utils/types";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { sleep } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { useEffect, useState, FluxDispatcher } from "@webpack/common";
|
||||
import { generateKeys, encryptData, decryptData } from "./rsa-utils";
|
||||
import { Devs } from "@utils/constants";
|
||||
import {
|
||||
RestAPI,
|
||||
|
@ -38,56 +40,6 @@ interface IMessageCreate {
|
|||
message: Message;
|
||||
}
|
||||
|
||||
// Generate RSA key pair
|
||||
function generateKeyPair(): { privateKey: string; publicKey: string; } {
|
||||
const keys = forge.pki.rsa.generateKeyPair({ bits: 2048 });
|
||||
const privateKey = forge.pki.privateKeyToPem(keys.privateKey);
|
||||
const publicKey = forge.pki.publicKeyToPem(keys.publicKey);
|
||||
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
// Encrypt message with public key
|
||||
function encrypt(message: string, publicKey): string[] {
|
||||
try {
|
||||
const publicKeyObj = forge.pki.publicKeyFromPem(publicKey);
|
||||
const chunkSize = 190;
|
||||
|
||||
const emojiRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
|
||||
message = message.replace(emojiRegex, '');
|
||||
|
||||
const encryptedChunks: string[] = [];
|
||||
|
||||
for (let i = 0; i < message.length; i += chunkSize) {
|
||||
const chunk = message.substring(i, i + chunkSize);
|
||||
const encryptedChunk = publicKeyObj.encrypt(chunk, 'RSA-OAEP', {
|
||||
md: forge.md.sha256.create(),
|
||||
});
|
||||
encryptedChunks.push(forge.util.encode64(encryptedChunk));
|
||||
}
|
||||
|
||||
return encryptedChunks;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt message with private key
|
||||
function decrypt(encryptedMessages: string[], privateKey): string {
|
||||
const privateKeyObj = forge.pki.privateKeyFromPem(privateKey);
|
||||
let decryptedMessages: string[] = [];
|
||||
|
||||
encryptedMessages.forEach((encryptedMessage) => {
|
||||
const encrypted = forge.util.decode64(encryptedMessage);
|
||||
const decrypted = privateKeyObj.decrypt(encrypted, 'RSA-OAEP', {
|
||||
md: forge.md.sha256.create(),
|
||||
});
|
||||
decryptedMessages.push(decrypted);
|
||||
});
|
||||
|
||||
return decryptedMessages.join('');
|
||||
}
|
||||
|
||||
// Chat Bar Icon Component
|
||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
[enabled, setEnabled] = useState(false);
|
||||
|
@ -107,7 +59,7 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
|||
const dmPromises = Object.keys(encryptcordGroupMembers).map(async (memberId) => {
|
||||
const groupMember = await UserUtils.getUser(memberId).catch(() => null);
|
||||
if (!groupMember) return;
|
||||
const encryptedMessage = encrypt(trimmedMessage, encryptcordGroupMembers[memberId]);
|
||||
const encryptedMessage = await encryptData(encryptcordGroupMembers[memberId], trimmedMessage);
|
||||
const encryptedMessageString = JSON.stringify(encryptedMessage);
|
||||
await sendTempMessage(groupMember.id, encryptedMessageString, `message`);
|
||||
});
|
||||
|
@ -185,14 +137,29 @@ export default definePlugin({
|
|||
async joinGroup(interaction) {
|
||||
const sender = await UserUtils.getUser(interaction.application_id).catch(() => null);
|
||||
if (!sender || sender.bot == true) return;
|
||||
if (interaction.data.component_type != 2 || interaction.data.custom_id != "acceptGroup") return;
|
||||
await sendTempMessage(interaction.application_id, `${await DataStore.get("encryptcordPublicKey")}`, "join");
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: interaction.channel_id,
|
||||
id: interaction.message_id,
|
||||
mlDeleted: true
|
||||
});
|
||||
if (interaction.data.component_type != 2) return;
|
||||
switch (interaction.data.custom_id) {
|
||||
case "acceptGroup":
|
||||
await sendTempMessage(interaction.application_id, `${await DataStore.get("encryptcordPublicKey")}`, "join");
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: interaction.channel_id,
|
||||
id: interaction.message_id,
|
||||
mlDeleted: true
|
||||
});
|
||||
break;
|
||||
case "removeFromGroup":
|
||||
await handleLeaving(sender.id, await DataStore.get("encryptcordGroupMembers") ?? {}, interaction.channel_id);
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: interaction.channel_id,
|
||||
id: interaction.message_id,
|
||||
mlDeleted: true
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
flux: {
|
||||
async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) {
|
||||
|
@ -232,7 +199,6 @@ export default definePlugin({
|
|||
const sender = await UserUtils.getUser(message.author.id).catch(() => null);
|
||||
if (!sender) return;
|
||||
const response = await fetch(message.attachments[0].url);
|
||||
console.log(response);
|
||||
const userKey = await response.text();
|
||||
await handleJoin(sender.id, userKey, encryptcordGroupMembers);
|
||||
return;
|
||||
|
@ -315,7 +281,7 @@ export default definePlugin({
|
|||
],
|
||||
async start() {
|
||||
addChatBarButton("Encryptcord", ChatBarIcon);
|
||||
const pair = generateKeyPair();
|
||||
const pair = await generateKeys();
|
||||
await DataStore.set('encryptcordPublicKey', pair.publicKey);
|
||||
await DataStore.set('encryptcordPrivateKey', pair.privateKey);
|
||||
if (await DataStore.get("encryptcordGroup") == true) {
|
||||
|
@ -402,7 +368,7 @@ async function handleLeaving(senderId: string, encryptcordGroupMembers: object,
|
|||
|
||||
// Handle receiving message
|
||||
async function handleMessage(message, senderId: string, groupChannel: string) {
|
||||
const decryptedMessage = decrypt(message, await DataStore.get("encryptcordPrivateKey"));
|
||||
const decryptedMessage = await decryptData(await DataStore.get("encryptcordPrivateKey"), message);
|
||||
await MessageActions.receiveMessage(groupChannel, await createMessage(decryptedMessage, senderId, groupChannel, 0));
|
||||
}
|
||||
|
||||
|
@ -442,7 +408,24 @@ async function handleJoin(senderId: string, senderKey: string, encryptcordGroupM
|
|||
});
|
||||
|
||||
await Promise.all(dmPromises);
|
||||
await MessageActions.receiveMessage(groupChannel, await createMessage("", senderId, groupChannel, 7));
|
||||
await MessageActions.receiveMessage(groupChannel, {
|
||||
...await createMessage("", senderId, groupChannel, 7), components: [{
|
||||
type: 1,
|
||||
components: [{
|
||||
type: 2,
|
||||
style: 4,
|
||||
label: 'I don\'t want to talk to you!',
|
||||
custom_id: 'removeFromGroup'
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
style: 2,
|
||||
label: '(Other users can still send/receive messages to/from them)',
|
||||
disabled: true,
|
||||
custom_id: 'encryptcord'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
// Create message for group
|
||||
|
|
116
src/plugins/encryptcord/rsa-utils.tsx
Normal file
116
src/plugins/encryptcord/rsa-utils.tsx
Normal file
|
@ -0,0 +1,116 @@
|
|||
export const generateKeys = async () => {
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: "SHA-256",
|
||||
},
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
|
||||
const exportedPublicKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
||||
const publicKey = formatPemKey(exportedPublicKey);
|
||||
|
||||
return { privateKey: keyPair.privateKey, publicKey };
|
||||
};
|
||||
|
||||
export const encryptData = async (pemPublicKey, data) => {
|
||||
const publicKey = await importPemPublicKey(pemPublicKey);
|
||||
|
||||
const chunkSize = 190;
|
||||
|
||||
const encryptedChunks: any[] = [];
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
for (let i = 0; i < data.length; i += chunkSize) {
|
||||
const chunk = await data.substring(i, i + chunkSize);
|
||||
const encryptedChunk = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
},
|
||||
publicKey,
|
||||
encoder.encode(chunk)
|
||||
);
|
||||
encryptedChunks.push(arrayBufferToBase64(encryptedChunk));
|
||||
}
|
||||
|
||||
return encryptedChunks;
|
||||
};
|
||||
|
||||
export const decryptData = async (privateKey, encArray) => {
|
||||
const decryptionPromises = encArray.map(async (encStr) => {
|
||||
const encBuffer = base64ToArrayBuffer(encStr);
|
||||
|
||||
const dec = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
},
|
||||
privateKey,
|
||||
encBuffer
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(dec);
|
||||
});
|
||||
|
||||
const decryptedMessages = await Promise.all(decryptionPromises);
|
||||
|
||||
return decryptedMessages.join('');
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
const arrayBufferToBase64 = (buffer) => {
|
||||
const binary = String.fromCharCode(...new Uint8Array(buffer));
|
||||
return btoa(binary);
|
||||
};
|
||||
|
||||
const base64ToArrayBuffer = (base64String) => {
|
||||
const binaryString = atob(base64String);
|
||||
const length = binaryString.length;
|
||||
const buffer = new ArrayBuffer(length);
|
||||
const view = new Uint8Array(buffer);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
view[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
const formatPemKey = (keyData) => {
|
||||
const base64Key = arrayBufferToBase64(keyData);
|
||||
return `-----BEGIN PUBLIC KEY-----\n` + base64Key + `\n----- END PUBLIC KEY----- `;
|
||||
};
|
||||
|
||||
const importPemPublicKey = async (pemKey) => {
|
||||
try {
|
||||
const trimmedPemKey = pemKey.trim();
|
||||
|
||||
const keyBody = trimmedPemKey
|
||||
.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replace("----- END PUBLIC KEY-----", "");
|
||||
|
||||
const binaryDer = atob(keyBody);
|
||||
|
||||
const arrayBuffer = new Uint8Array(binaryDer.length);
|
||||
for (let i = 0; i < binaryDer.length; i++) {
|
||||
arrayBuffer[i] = binaryDer.charCodeAt(i);
|
||||
}
|
||||
|
||||
return await crypto.subtle.importKey(
|
||||
"spki",
|
||||
arrayBuffer,
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
hash: { name: "SHA-256" },
|
||||
},
|
||||
true,
|
||||
["encrypt"]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error importing PEM public key:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
Loading…
Add table
Reference in a new issue