mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-25 07:48: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",
|
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||||
"monaco-editor": "^0.43.0",
|
"monaco-editor": "^0.43.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"node-forge": "^1.3.1",
|
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
@ -37,9 +37,6 @@ dependencies:
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^4.0.2
|
specifier: ^4.0.2
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
node-forge:
|
|
||||||
specifier: ^1.3.1
|
|
||||||
version: 1.3.1
|
|
||||||
virtual-merge:
|
virtual-merge:
|
||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
|
@ -2483,11 +2480,6 @@ packages:
|
||||||
whatwg-url: 5.0.0
|
whatwg-url: 5.0.0
|
||||||
dev: true
|
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:
|
/normalize-package-data@2.5.0:
|
||||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
||||||
import { removeButton } from "@api/MessagePopover";
|
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 * as DataStore from "@api/DataStore";
|
||||||
import { sleep } from "@utils/misc";
|
import { sleep } from "@utils/misc";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||||
import { useEffect, useState, FluxDispatcher } from "@webpack/common";
|
import { useEffect, useState, FluxDispatcher } from "@webpack/common";
|
||||||
|
import { generateKeys, encryptData, decryptData } from "./rsa-utils";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import {
|
import {
|
||||||
RestAPI,
|
RestAPI,
|
||||||
|
@ -38,56 +40,6 @@ interface IMessageCreate {
|
||||||
message: Message;
|
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
|
// Chat Bar Icon Component
|
||||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
[enabled, setEnabled] = useState(false);
|
[enabled, setEnabled] = useState(false);
|
||||||
|
@ -107,7 +59,7 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
const dmPromises = Object.keys(encryptcordGroupMembers).map(async (memberId) => {
|
const dmPromises = Object.keys(encryptcordGroupMembers).map(async (memberId) => {
|
||||||
const groupMember = await UserUtils.getUser(memberId).catch(() => null);
|
const groupMember = await UserUtils.getUser(memberId).catch(() => null);
|
||||||
if (!groupMember) return;
|
if (!groupMember) return;
|
||||||
const encryptedMessage = encrypt(trimmedMessage, encryptcordGroupMembers[memberId]);
|
const encryptedMessage = await encryptData(encryptcordGroupMembers[memberId], trimmedMessage);
|
||||||
const encryptedMessageString = JSON.stringify(encryptedMessage);
|
const encryptedMessageString = JSON.stringify(encryptedMessage);
|
||||||
await sendTempMessage(groupMember.id, encryptedMessageString, `message`);
|
await sendTempMessage(groupMember.id, encryptedMessageString, `message`);
|
||||||
});
|
});
|
||||||
|
@ -185,14 +137,29 @@ export default definePlugin({
|
||||||
async joinGroup(interaction) {
|
async joinGroup(interaction) {
|
||||||
const sender = await UserUtils.getUser(interaction.application_id).catch(() => null);
|
const sender = await UserUtils.getUser(interaction.application_id).catch(() => null);
|
||||||
if (!sender || sender.bot == true) return;
|
if (!sender || sender.bot == true) return;
|
||||||
if (interaction.data.component_type != 2 || interaction.data.custom_id != "acceptGroup") return;
|
if (interaction.data.component_type != 2) return;
|
||||||
await sendTempMessage(interaction.application_id, `${await DataStore.get("encryptcordPublicKey")}`, "join");
|
switch (interaction.data.custom_id) {
|
||||||
FluxDispatcher.dispatch({
|
case "acceptGroup":
|
||||||
type: "MESSAGE_DELETE",
|
await sendTempMessage(interaction.application_id, `${await DataStore.get("encryptcordPublicKey")}`, "join");
|
||||||
channelId: interaction.channel_id,
|
FluxDispatcher.dispatch({
|
||||||
id: interaction.message_id,
|
type: "MESSAGE_DELETE",
|
||||||
mlDeleted: true
|
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: {
|
flux: {
|
||||||
async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) {
|
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);
|
const sender = await UserUtils.getUser(message.author.id).catch(() => null);
|
||||||
if (!sender) return;
|
if (!sender) return;
|
||||||
const response = await fetch(message.attachments[0].url);
|
const response = await fetch(message.attachments[0].url);
|
||||||
console.log(response);
|
|
||||||
const userKey = await response.text();
|
const userKey = await response.text();
|
||||||
await handleJoin(sender.id, userKey, encryptcordGroupMembers);
|
await handleJoin(sender.id, userKey, encryptcordGroupMembers);
|
||||||
return;
|
return;
|
||||||
|
@ -315,7 +281,7 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
async start() {
|
async start() {
|
||||||
addChatBarButton("Encryptcord", ChatBarIcon);
|
addChatBarButton("Encryptcord", ChatBarIcon);
|
||||||
const pair = generateKeyPair();
|
const pair = await generateKeys();
|
||||||
await DataStore.set('encryptcordPublicKey', pair.publicKey);
|
await DataStore.set('encryptcordPublicKey', pair.publicKey);
|
||||||
await DataStore.set('encryptcordPrivateKey', pair.privateKey);
|
await DataStore.set('encryptcordPrivateKey', pair.privateKey);
|
||||||
if (await DataStore.get("encryptcordGroup") == true) {
|
if (await DataStore.get("encryptcordGroup") == true) {
|
||||||
|
@ -402,7 +368,7 @@ async function handleLeaving(senderId: string, encryptcordGroupMembers: object,
|
||||||
|
|
||||||
// Handle receiving message
|
// Handle receiving message
|
||||||
async function handleMessage(message, senderId: string, groupChannel: string) {
|
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));
|
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 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
|
// 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