Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions extension/js/common/message-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class MessageRenderer {
private static async renderPgpSignatureCheckResult(
renderModule: RenderInterface,
verifyRes: VerifyRes | undefined,
wasSignerEmailSupplied: boolean,
senderEmail: string | undefined,
retryVerification?: () => Promise<VerifyRes | undefined>
) {
if (verifyRes?.error) {
Expand Down Expand Up @@ -188,25 +188,37 @@ export class MessageRenderer {
retryVerificationAgain = retryVerification;
}
}
await MessageRenderer.renderPgpSignatureCheckResult(renderModule, verifyRes, wasSignerEmailSupplied, retryVerificationAgain);
await MessageRenderer.renderPgpSignatureCheckResult(renderModule, verifyRes, senderEmail, retryVerificationAgain);
return;
} else if (!wasSignerEmailSupplied) {
} else if (!senderEmail) {
// todo: unit-test this case?
renderModule.renderSignatureStatus('could not verify signature: missing pubkey, missing sender info');
} else {
MessageRenderer.renderMissingPubkeyOrBadSignature(renderModule, verifyRes);
await MessageRenderer.renderMissingPubkeyOrBadSignature(renderModule, verifyRes, senderEmail);
}
}

private static renderMissingPubkeyOrBadSignature(renderModule: RenderInterfaceBase, verifyRes: VerifyRes): void {
private static async renderMissingPubkeyOrBadSignature(renderModule: RenderInterfaceBase, verifyRes: VerifyRes, senderEmail: string): Promise<void> {
// eslint-disable-next-line no-null/no-null
if (verifyRes.match === null || !Value.arr.hasIntersection(verifyRes.signerLongids, verifyRes.suppliedLongids)) {
MessageRenderer.renderMissingPubkey(renderModule, verifyRes.signerLongids[0]);
const signerLongid = verifyRes.signerLongids[0];
const signerEmails = await ContactStore.getEmailsByLongid(undefined, signerLongid);
const signerEmailNotMatchingSender = signerEmails.find(e => e !== senderEmail);
if (signerEmailNotMatchingSender) {
MessageRenderer.renderSignerSenderMismatch(renderModule, senderEmail, signerEmailNotMatchingSender);
} else {
MessageRenderer.renderMissingPubkey(renderModule, signerLongid);
}
} else {
MessageRenderer.renderBadSignature(renderModule);
}
}

private static renderSignerSenderMismatch(renderModule: RenderInterfaceBase, senderEmail: string, signerEmail: string) {
renderModule.renderSignatureStatus(`could not verify signature: signed by ${signerEmail} but message is from ${senderEmail}`);
renderModule.setFrameColor('red');
}

private static renderMissingPubkey(renderModule: RenderInterfaceBase, signerLongid: string) {
renderModule.renderSignatureStatus(`could not verify signature: missing pubkey ${signerLongid}`);
}
Expand Down Expand Up @@ -613,7 +625,7 @@ export class MessageRenderer {
}
decryptedContent = this.clipMessageIfLimitExceeds(decryptedContent);
renderModule.separateQuotedContentAndRenderText(decryptedContent, isHtml, isChecksumInvalid);
await MessageRenderer.renderPgpSignatureCheckResult(renderModule, sigResult, Boolean(signerEmail), retryVerification);
await MessageRenderer.renderPgpSignatureCheckResult(renderModule, sigResult, signerEmail, retryVerification);
if (renderableAttachments.length) {
renderModule.renderInnerAttachments(renderableAttachments, isEncrypted);
}
Expand Down
33 changes: 33 additions & 0 deletions extension/js/common/platform/store/contact-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,39 @@ export class ContactStore extends AbstractStore {
return (await ContactStore.extractPubkeys(db, fingerprints)).map(pubkey => pubkey.armoredKey).filter(Boolean);
}

public static async getEmailsByLongid(db: IDBDatabase | undefined, longid: string): Promise<string[]> {
if (!db) {
return (await BrowserMsg.retryOnBgNotReadyErr(() => BrowserMsg.send.bg.await.db({ f: 'getEmailsByLongid', args: [longid] }))) as string[];
}
const tx = db.transaction(['pubkeys', 'emails'], 'readonly');
const emails: string[] = [];
await new Promise<void>((resolve, reject) => {
const req = tx.objectStore('pubkeys').index('index_longids').getAll(longid);
ContactStore.setReqPipe(
req,
(pubkeys: Pubkey[]) => {
if (!pubkeys.length) {
resolve();
return;
}
ContactStore.setTxHandlers(tx, () => resolve(), reject);
for (const pubkey of pubkeys) {
const req2 = tx.objectStore('emails').index('index_fingerprints').getAll(pubkey.fingerprint);
ContactStore.setReqPipe(req2, (emailEntities: Email[]) => {
for (const entity of emailEntities) {
if (!emails.includes(entity.email)) {
emails.push(entity.email);
}
}
});
}
},
reject
);
});
return emails;
}

public static async getOneWithAllPubkeys(db: IDBDatabase | undefined, email: string): Promise<ContactInfoWithSortedPubkeys | undefined> {
if (!db) {
// relay op through background process
Expand Down
29 changes: 27 additions & 2 deletions test/source/tests/decrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te
const { acctEmail, authHdr } = await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility');
const expectedMessage = {
encryption: 'not encrypted',
signature: 'could not verify signature: missing pubkey ADAC279C95093207',
signature: 'could not verify signature: signed by flowcrypt.compatibility@gmail.com but message is from sender@domain.com',
content: ['flowcrypt-browser issue #5029 test email'],
};
const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}&threadId=${threadId}`);
Expand Down Expand Up @@ -542,7 +542,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te
{
content: ['this was encrypted with gpg', 'gpg --sign --armor -r flowcrypt.compatibility@gmail.com ./text.txt'],
encryption: 'not encrypted',
signature: 'could not verify signature: missing pubkey 7FDE685548AEA788',
signature: 'could not verify signature: signed by flowcrypt.compatibility@gmail.com but message is from sender@domain.com',
quoted: false,
},
authHdr
Expand Down Expand Up @@ -1535,6 +1535,31 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);

test(
'signature - shows sender/signer mismatch when signer key belongs to a different email',
testWithBrowser(async (t, browser) => {
const threadId = '1766644f13510f58';
const { authHdr } = await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'ci.tests.gmail');
const dbPage = await browser.newExtensionPage(t, 'chrome/dev/ci_unit_test.htm');
await dbPage.page.evaluate(async (pubkey: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const key = await (window as any).KeyUtil.parse(pubkey);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await (window as any).ContactStore.update(undefined, 'actual.signer@example.com', { pubkey: key });
}, testConstants.pubkey2864E326A5BE488A);
await dbPage.close();
const gmailPage = await browser.newPage(t, `${t.context.urls?.mockGmailUrl()}/${threadId}`, undefined, authHdr);
await gmailPage.waitAll('iframe', { timeout: 2 });
const pgpBlock = await gmailPage.getFrame(['/chrome/elements/pgp_block.htm'], { sleep: 10, timeout: 20 });
const expectedMessage = {
content: ['How is my message signed?'],
encryption: 'not encrypted',
signature: 'could not verify signature: signed by actual.signer@example.com but message is from sender@example.com',
};
await BrowserRecipe.pgpBlockCheck(t, pgpBlock, expectedMessage);
})
);

test(
'decrypt - protonmail - PGP/inline signed and encrypted message with pubkey - pubkey signature is ignored',
testWithBrowser(async (t, browser) => {
Expand Down
Loading