diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bf4d12..c4aecfc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/manifest.json b/manifest.json index 754b8e6..9559f81 100644 --- a/manifest.json +++ b/manifest.json @@ -7,7 +7,7 @@ "discordID": "372148345894076416", "github": "SammCheese" }, - "version": "1.3.3", + "version": "1.4.0", "updater": { "type": "github", "id": "SammCheese/RepluggedPGP" diff --git a/package.json b/package.json index 072df01..0c375f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "repluggedpgp", - "version": "1.3.3", + "version": "1.4.0", "description": "Encrypt, Sign and Decrypt messages with the power of GPG", "engines": { "node": ">=14.0.0" @@ -32,7 +32,7 @@ "eslint-plugin-react": "^7.31.10", "openpgp": "^5.5.0", "prettier": "^2.8.1", - "replugged": "4.0.0-beta0.28", + "replugged": "4.0.0-rc.3", "typescript": "^4.8.4" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b96e388..fb05e7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ specifiers: idb-keyval: ^6.2.0 openpgp: ^5.5.0 prettier: ^2.8.1 - replugged: 4.0.0-beta0.28 + replugged: 4.0.0-rc.3 typescript: ^4.8.4 dependencies: @@ -31,7 +31,7 @@ devDependencies: eslint-plugin-react: 7.31.10_eslint@8.25.0 openpgp: 5.5.0 prettier: 2.8.1 - replugged: 4.0.0-beta0.28 + replugged: 4.0.0-rc.3 typescript: 4.8.4 packages: @@ -3272,8 +3272,8 @@ packages: engines: {node: '>=8'} dev: true - /replugged/4.0.0-beta0.28: - resolution: {integrity: sha512-AcKxp03d/eqHWHc1cHUng4X7+ACCiLweyR5nCLOMTF6CrZX+G1ah2HA8raDZssCbCvmO8S6PejgsYShDNuH5/g==} + /replugged/4.0.0-rc.3: + resolution: {integrity: sha512-G26boVV9zn32GzvLDx9YOcUowETdD59BNMBu183hwvU2h22fXjD8zeaQlPtqxf0gJO8q1G9EC/c9zN8FafgSQA==} engines: {node: '>=14.0.0'} hasBin: true dependencies: diff --git a/src/index.ts b/src/index.ts index a272ca3..eb1edcc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,9 @@ -import { Injector, common, types } from "replugged"; +import { Injector, common, types, webpack } from "replugged"; import { PGPSettings, decryptMessage, encryptMessage, getKey, - getKeyUserInfo, initSettings, parseMessageFileContent, pgpFormat, @@ -18,9 +17,8 @@ import { popoverIcon } from "./assets/PopoverIcon"; import { PGPToggleButton } from "./assets/ToggleButton"; import { PGPCONSTS } from "./constants"; -import { DiscordMessage, RPGPWindow } from "./repluggedpgp"; +import { DiscordMessage, Messages, RPGPWindow } from "./repluggedpgp"; import { buildAddKeyModal } from "./components/AddKey"; -import { buildPGPResult } from "./components/PGPResult"; import { del } from "idb-keyval"; const injector = new Injector(); @@ -32,6 +30,7 @@ export async function start(): Promise { // Initialize Settings await initSettings(); await injectSendMessage(); + await resetSettings(); window.RPGP = { PGPToggleButton, @@ -50,36 +49,45 @@ async function receiver(message: DiscordMessage): Promise { tempContent = await parseMessageFileContent(message?.attachments[0].url); if (tempContent.includes(PGPCONSTS.PGP_MESSAGE_HEADER)) { - let result; + //let totalResult = "Wrong Password or no secret key. Was this message meant for you?"; + let signature; + try { - const decrypted = await decryptMessage(tempContent); - result = decrypted.decrypted; - } catch { - result = "Wrong Password or no Secret Key!"; + let result = await decryptMessage(tempContent); + + // If its not signed, skip all additional steps and immediately set the content + if (!result?.signatures[0]) { + message.content = + result?.decrypted ?? "Wrong Password or no secret key. Was this message meant for you?"; + return; + } + + signature = result?.match; + + // No signature, it failed to verify + if (!signature) { + message.content = `${ + result?.decrypted ?? "Wrong Password or no secret key. Was this message meant for you?" + }\n\`\`\`\nFailed to verify Message\n\`\`\``; + return; + } + + const verifiedUser = result?.match.users[0].userID.userID; + const fingerprint = result?.match?.keyPacket?.keyID?.toHex()?.toUpperCase(); + message.content = `${result.decrypted}\n\`\`\`\nSuccessfully verified message from ${verifiedUser} (${fingerprint})\n\`\`\``; + } catch (error) { + console.error(error); + message.content = `Failed to decrypt message: ${error}`; } - buildPGPResult({ pgpresult: result }); } if (tempContent.includes(PGPCONSTS.PGP_SIGN_HEADER)) { - const pubKeys = PGPSettings.get("savedPubKeys", []); - - let sigVerification = "Failed to validate Message"; - - for (let i = 0; i < pubKeys.length; i++) { - try { - const { verified, keyID } = await verifyMessage(tempContent, pubKeys[i].publicKey); - if (await verified) { - const keyUser = await getKeyUserInfo(pubKeys[i].publicKey); - sigVerification = `Successfully validated Message from ${keyUser?.userID}\n(${keyID - .toHex() - .toUpperCase()})`; - break; - } - } catch {} - } + let sigVerification = await verifyMessage(tempContent); message.content = sigVerification.includes("Successfully") - ? tempContent.replace(PGPCONSTS.PGP_SIGNED_REGEX, `$3\`\`\`\n${sigVerification}\n\`\`\``) + ? tempContent + .replace(PGPCONSTS.PGP_SIGNED_REGEX, `$3\`\`\`\n${sigVerification}\n\`\`\``) + .replaceAll("- -", "-") : (message.content += `\`\`\`\n${sigVerification}\n\`\`\``); } @@ -91,14 +99,21 @@ async function receiver(message: DiscordMessage): Promise { } } -// eslint-disable-next-line @typescript-eslint/require-await async function injectSendMessage(): Promise { - injector.instead(common.messages, "sendMessage", async (args, fn) => { + const sendMessageModule = await webpack.waitForModule( + webpack.filters.byProps("sendMessage", "editMessage", "deleteMessage"), + ); + + if (!sendMessageModule) return; + + injector.instead(sendMessageModule, "sendMessage", async (args, fn) => { const { signingActive, encryptionActive, asFile, onlyOnce } = PGPSettings.all(); - if (encryptionActive) args[1].content = await encryptMessage(args[1].content); + // We sign the + if (encryptionActive) args[1].content = await encryptMessage(args[1].content, signingActive); - if (signingActive) args[1].content = await signMessage(args[1].content); + // Sign Message normally if no encryption active + if (signingActive && !encryptionActive) args[1].content = await signMessage(args[1].content); // premiumType, 0 - No nitro, 1 - Nitro Classic, 2 - Nitro, 3 - Nitro Basic const isNitro = common.users.getCurrentUser().premiumType === 2; diff --git a/src/repluggedpgp.d.ts b/src/repluggedpgp.d.ts index 387930c..5afdbe7 100644 --- a/src/repluggedpgp.d.ts +++ b/src/repluggedpgp.d.ts @@ -42,6 +42,7 @@ interface addFileType { interface decryptMessageType { decrypted: string; signatures: VerificationResult[]; + match?: false | PublicKey; } interface Attachment { @@ -61,3 +62,12 @@ interface DiscordMessage { embeds: DiscordEmbed[]; attachments?: Attachment[]; } + +interface Messages { + sendMessage: ( + channelId: string, + message: OutgoingMessage, + promise?: boolean, + options?: OutgoingMessageOptions, + ) => void; +} diff --git a/src/utils.ts b/src/utils.ts index e9495c7..0839235 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import { pgp } from "./lib"; import { get } from "idb-keyval"; import { common, settings, types, webpack } from "replugged"; import { buildKeyPass } from "./components/KeyPassword"; -import { MaybeArray, PrivateKey, PublicKey, UserIDPacket, VerificationResult } from "openpgp"; +import { KeyID, MaybeArray, PrivateKey, PublicKey, UserIDPacket } from "openpgp"; import { addFileType, decryptMessageType, savedPubKeyType } from "./repluggedpgp"; import { buildRecipientSelection } from "./components/RecipientSelection"; @@ -105,52 +105,89 @@ async function getPrivateKey(retries = 3): Promise> { export async function signMessage(message: string): Promise { const unsigned = await pgp.createCleartextMessage({ text: message }); const signingKey = await getPrivateKey(); - const signed = await pgp.sign({ + const signed: string = await pgp.sign({ message: unsigned, signingKeys: signingKey, }); return signed; } -export async function verifyMessage( - ctMessage: string, - publicKey: string, -): Promise { +export async function verifyMessage(ctMessage: string): Promise { + const pubKeys = PGPSettings.get("savedPubKeys", []); + const signedMessage = await pgp.readCleartextMessage({ cleartextMessage: ctMessage }); - // // @ts-expect-error Typemismatch https://github.com/openpgpjs/openpgpjs/issues/1582 - const vResult = await pgp.verify({ - // // @ts-expect-error Typemismatch - message: signedMessage, - verificationKeys: await getKey(publicKey), - }); + let sigVerification = "Failed to validate Message"; + + for (let i = 0; i < pubKeys.length; i++) { + try { + // // @ts-expect-error Typemismatch https://github.com/openpgpjs/openpgpjs/issues/1582 + const vResult = await pgp.verify({ + // // @ts-expect-error Typemismatch + message: signedMessage, + verificationKeys: await getKey(pubKeys[i].publicKey), + }); + + const { verified, keyID } = vResult.signatures[0]; - return vResult.signatures[0]; + if (await verified) { + console.log("Success", verified, keyID); + const keyUser = await getKeyUserInfo(pubKeys[i].publicKey); + sigVerification = `Successfully Validated message from ${keyUser?.userID}\n(${keyID + .toHex() + .toString() + .toUpperCase()})`; + } + } catch {} + } + return sigVerification; } -export async function encryptMessage(message: string): Promise { +export async function encryptMessage(message: string, sign?: boolean): Promise { const recipients = await buildRecipientSelection(); const publicKeys = await Promise.all(recipients.map((armoredKey) => pgp.readKey({ armoredKey }))); return await pgp.encrypt({ message: await pgp.createMessage({ text: message }), encryptionKeys: publicKeys, + // eslint-disable-next-line no-undefined + signingKeys: sign ? await getPrivateKey() : undefined, }); } -export async function decryptMessage(message: string): Promise { - return await new Promise(async (resolve, reject) => { - const readMessage = await pgp.readMessage({ armoredMessage: message }); - try { - const { data: decrypted, signatures } = await pgp.decrypt({ - message: readMessage, - decryptionKeys: await getPrivateKey(), - }); - resolve({ decrypted, signatures }); - } catch (e) { - reject(e); +export async function decryptMessage(message: string): Promise { + const readMessage = await pgp.readMessage({ armoredMessage: message }); + const key = await getPrivateKey(); + + try { + const { data: decrypted, signatures } = await pgp.decrypt({ + message: readMessage, + decryptionKeys: key, + }); + + if (!signatures[0]?.keyID) return { decrypted, signatures }; + + const match = await findPubKeyWithKeyID(signatures[0].keyID); + + if (!match) return { decrypted, signatures, match }; + + return { decrypted, signatures, match: await getKey(match) }; + } catch {} +} + +async function findPubKeyWithKeyID(targetKey: KeyID): Promise { + const keys = PGPSettings.get("savedPubKeys", []); + + if (!keys) return false; + + for (let i = 0; i < keys.length; i++) { + const key = await getKey(keys[i].publicKey); + console.log(targetKey, key); + if (key.getKeyID().equals(targetKey)) { + return keys[i].publicKey; } - }); + } + return false; } export async function parseMessageFileContent(url: string): Promise {