Skip to content

Commit

Permalink
Fix sending functionality, fix signing and encryption (closes #1 and #2)
Browse files Browse the repository at this point in the history
  • Loading branch information
SammCheese committed Mar 10, 2023
1 parent f8f53a2 commit 09e6c9d
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 65 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"discordID": "372148345894076416",
"github": "SammCheese"
},
"version": "1.3.3",
"version": "1.4.0",
"updater": {
"type": "github",
"id": "SammCheese/RepluggedPGP"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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": {
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 46 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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();
Expand All @@ -32,6 +30,7 @@ export async function start(): Promise<void> {
// Initialize Settings
await initSettings();
await injectSendMessage();
await resetSettings();

window.RPGP = {
PGPToggleButton,
Expand All @@ -50,36 +49,45 @@ async function receiver(message: DiscordMessage): Promise<void> {
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\`\`\``);
}

Expand All @@ -91,14 +99,21 @@ async function receiver(message: DiscordMessage): Promise<void> {
}
}

// eslint-disable-next-line @typescript-eslint/require-await
async function injectSendMessage(): Promise<void> {
injector.instead(common.messages, "sendMessage", async (args, fn) => {
const sendMessageModule = await webpack.waitForModule<types.RawModule & Messages>(
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;
Expand Down
10 changes: 10 additions & 0 deletions src/repluggedpgp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface addFileType {
interface decryptMessageType {
decrypted: string;
signatures: VerificationResult[];
match?: false | PublicKey;
}

interface Attachment {
Expand All @@ -61,3 +62,12 @@ interface DiscordMessage {
embeds: DiscordEmbed[];
attachments?: Attachment[];
}

interface Messages {
sendMessage: (
channelId: string,
message: OutgoingMessage,
promise?: boolean,
options?: OutgoingMessageOptions,
) => void;
}
89 changes: 63 additions & 26 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -105,52 +105,89 @@ async function getPrivateKey(retries = 3): Promise<MaybeArray<PrivateKey>> {
export async function signMessage(message: string): Promise<string> {
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<VerificationResult> {
export async function verifyMessage(ctMessage: string): Promise<string> {
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<string> {
export async function encryptMessage(message: string, sign?: boolean): Promise<string> {
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<decryptMessageType> {
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<decryptMessageType | undefined> {
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<string | false> {
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<string> {
Expand Down

0 comments on commit 09e6c9d

Please sign in to comment.