From 5d53fc9f548ff4489bab515235b40bc55491ace0 Mon Sep 17 00:00:00 2001 From: Ioan Moldovan Date: Tue, 29 Aug 2023 05:14:12 -0400 Subject: [PATCH] #2183 Blind carbon copy leak fix (#2329) * fix: blink carbon copy link * fix: remove unused code * fix: ui test * fix: pr reviews --- FlowCrypt/Core/CoreTypes.swift | 4 +- .../ComposeMessageHelper.swift | 7 ++- FlowCryptAppTests/Core/CoreTypesTest.swift | 3 +- appium/api-mocks/apis/fes/fes-endpoints.ts | 27 ++++++++--- appium/api-mocks/lib/api.ts | 1 + appium/api-mocks/lib/configuration-types.ts | 7 +++ ...eckPasswordProtectedMessageBccLeak.spec.ts | 48 +++++++++++++++++++ 7 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 appium/tests/specs/mock/composeEmail/CheckPasswordProtectedMessageBccLeak.spec.ts diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index fd45f5aa8..9a2b36c7c 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -139,13 +139,13 @@ struct SendableMsg: Equatable { } extension SendableMsg { - func copy(body: SendableMsgBody, atts: [Attachment], pubKeys: [String]?) -> SendableMsg { + func copy(body: SendableMsgBody, atts: [Attachment], pubKeys: [String]?, includeBcc: Bool = true) -> SendableMsg { SendableMsg( text: body.text, html: body.html, to: self.to, cc: self.cc, - bcc: self.bcc, + bcc: includeBcc ? bcc : [], from: self.from, subject: self.subject, replyToMsgId: self.replyToMsgId, diff --git a/FlowCrypt/Functionality/Services/Compose Message Helper/ComposeMessageHelper.swift b/FlowCrypt/Functionality/Services/Compose Message Helper/ComposeMessageHelper.swift index 840d5aba8..ee89b4dd3 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Helper/ComposeMessageHelper.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Helper/ComposeMessageHelper.swift @@ -352,7 +352,8 @@ extension ComposeMessageHelper { let sendableMsg = message.copy( body: messageBody, atts: [encryptedBodyAttachment] + encryptedAttachments, - pubKeys: nil + pubKeys: nil, + includeBcc: true ) return try await composeEmail(msg: sendableMsg) @@ -404,7 +405,9 @@ extension ComposeMessageHelper { let msgWithReplyToken = message.copy( body: bodyWithReplyToken, atts: message.atts, - pubKeys: nil + pubKeys: nil, + // Fix blind carbon copy leak. ref: https://github.com/FlowCrypt/flowcrypt-ios/issues/2183 + includeBcc: false ) let pgpMimeWithAttachments = try await composeEmail(msg: msgWithReplyToken) diff --git a/FlowCryptAppTests/Core/CoreTypesTest.swift b/FlowCryptAppTests/Core/CoreTypesTest.swift index bddc67360..e3d5fd3ff 100644 --- a/FlowCryptAppTests/Core/CoreTypesTest.swift +++ b/FlowCryptAppTests/Core/CoreTypesTest.swift @@ -42,7 +42,8 @@ class CoreTypesTest: XCTestCase { let msgCopy = msg.copy( body: copyBody, atts: copyAttachments, - pubKeys: copyPubKeys + pubKeys: copyPubKeys, + includeBcc: true ) XCTAssertEqual(msgCopy.text, copyBody.text) diff --git a/appium/api-mocks/apis/fes/fes-endpoints.ts b/appium/api-mocks/apis/fes/fes-endpoints.ts index a8003d7a3..da4e73237 100644 --- a/appium/api-mocks/apis/fes/fes-endpoints.ts +++ b/appium/api-mocks/apis/fes/fes-endpoints.ts @@ -69,13 +69,26 @@ export const getMockFesEndpoints = (mockConfig: MockConfig, fesConfig: FesConfig // body is a mime-multipart string, we're doing a few smoke checks here without parsing it if (req.method === 'POST') { expectContains(body, '-----BEGIN PGP MESSAGE-----'); - expectContains(body, '"associateReplyToken":"mock-fes-reply-token"'); - expectContains(body, '"to":["to@example.com"]'); - expectContains(body, '"cc":[]'); - expectContains(body, '"bcc":["bcc@example.com"]'); - authenticate(req); - expectContains(body, '"from":"user@disablefesaccesstoken.test:8001"'); - return { url: `${mockConfig}/message/FES-MOCK-MESSAGE-ID` }; + const match = String(body).match(/Content-Type: application\/json\s*\n\s*(\{.*\})/); + + if (!match) { + throw new FesHttpErr('Bad request', Status.BAD_REQUEST); + } + const messageData = JSON.parse(match[0]); + const { associateReplyToken, to, cc, bcc } = messageData; + + expect(associateReplyToken).toBe('mock-fes-reply-token'); + + if (fesConfig.messageUploadCheck) { + const { to: toCheck, cc: ccCheck, bcc: bccCheck } = fesConfig.messageUploadCheck; + if (toCheck) expect(to).toBe(toCheck); + if (ccCheck) expect(cc).toBe(ccCheck); + if (bccCheck) expect(bcc).toBe(bccCheck); + } + + return { + url: `https://flowcrypt.com/shared-tenant-fes/message/6da5ea3c-d2d6-4714-b15e-f29c805e5c6a`, + }; } throw new FesHttpErr('Not Found', Status.NOT_FOUND); }, diff --git a/appium/api-mocks/lib/api.ts b/appium/api-mocks/lib/api.ts index c221223ac..5e51d81f0 100644 --- a/appium/api-mocks/lib/api.ts +++ b/appium/api-mocks/lib/api.ts @@ -262,6 +262,7 @@ export class Api { req.url!.startsWith('/api/message/upload') || // flowcrypt.com/api pwd msg (req.url!.startsWith('/attester/pub/') && req.method === 'POST') || // attester submit req.url!.startsWith('/api/v1/message') || // FES pwd msg + req.url!.startsWith('/fes/api/v1/message') || // Shared TENANT FES pwd msg req.url!.startsWith('/token') // gmail auth token ) { parsedBody = body.toString(); diff --git a/appium/api-mocks/lib/configuration-types.ts b/appium/api-mocks/lib/configuration-types.ts index 915c6eed7..fff0d7df0 100644 --- a/appium/api-mocks/lib/configuration-types.ts +++ b/appium/api-mocks/lib/configuration-types.ts @@ -32,8 +32,15 @@ type Fes$ClientConfiguration = { enforce_keygen_expire_months?: number; }; +type FesMessageUploadCheck = { + to?: string[]; + cc?: string[]; + bcc?: string[]; +}; + export type FesConfig = { returnError?: { code: number; message: string; format?: 'wrong-json' | 'wrong-text' }; + messageUploadCheck?: FesMessageUploadCheck; clientConfiguration?: Fes$ClientConfiguration; }; diff --git a/appium/tests/specs/mock/composeEmail/CheckPasswordProtectedMessageBccLeak.spec.ts b/appium/tests/specs/mock/composeEmail/CheckPasswordProtectedMessageBccLeak.spec.ts new file mode 100644 index 000000000..c1d60abd6 --- /dev/null +++ b/appium/tests/specs/mock/composeEmail/CheckPasswordProtectedMessageBccLeak.spec.ts @@ -0,0 +1,48 @@ +import { MailFolderScreen, NewMessageScreen, SetupKeyScreen, SplashScreen } from '../../../screenobjects/all-screens'; + +import { MockApi } from 'api-mocks/mock'; +import { MockApiConfig } from 'api-mocks/mock-config'; +import { CommonData } from '../../../data'; + +describe('COMPOSE EMAIL: ', () => { + it('check sending password protected message bcc leakage', async () => { + const recipient = CommonData.recipientWithoutPublicKey.email; + const emailSubject = CommonData.recipientWithoutPublicKey.subject; + const emailText = CommonData.simpleEmail.message; + const emailPassword = CommonData.recipientWithoutPublicKey.password; + const bcc = 'test@example.com'; + + const mockApi = new MockApi(); + + mockApi.ekmConfig = MockApiConfig.defaultEnterpriseEkmConfiguration; + mockApi.fesConfig = { + ...MockApiConfig.defaultEnterpriseFesConfiguration, + messageUploadCheck: { + to: [recipient], + cc: [], + bcc: [], + }, + }; + mockApi.attesterConfig = { + servedPubkeys: {}, + }; + + await mockApi.withMockedApis(async () => { + await SplashScreen.mockLogin(); + await SetupKeyScreen.setPassPhrase(); + await MailFolderScreen.checkInboxScreen(); + + await MailFolderScreen.clickCreateEmail(); + await NewMessageScreen.composeEmail(recipient, emailSubject, emailText, undefined, bcc); + await NewMessageScreen.checkFilledComposeEmailInfo({ + recipients: [recipient], + subject: emailSubject, + message: emailText, + bcc: [bcc], + }); + await NewMessageScreen.clickPasswordCell(); + await NewMessageScreen.setMessagePassword(emailPassword); + await NewMessageScreen.clickSendButton(); + }); + }); +});