From 248a4d326e05b869023484a4e8b0f726bd4c8057 Mon Sep 17 00:00:00 2001 From: Personal Date: Thu, 30 Sep 2021 22:12:33 +0300 Subject: [PATCH 1/4] Added decrypting attachment; --- FlowCrypt.xcodeproj/project.pbxproj | 2 +- FlowCrypt/Core/Core.swift | 6 +- .../Message Provider/MessageService.swift | 35 ++++++-- .../Core/FlowCryptCoreTests.swift | 83 ++++++++++++++++++- .../Extensions/StringExtensions.swift | 14 ++++ 5 files changed, 130 insertions(+), 10 deletions(-) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index af7b53f88..69ef4148b 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -1321,8 +1321,8 @@ isa = PBXGroup; children = ( 9F4163EC266574CB00106194 /* BackupServiceMock.swift */, - 9F6F3C3B26ADFBC7005BD9C6 /* CoreComposeMessageMock.swift */, 9F6F3C6926ADFBEB005BD9C6 /* MessageGatewayMock.swift */, + 9F6F3C3B26ADFBC7005BD9C6 /* CoreComposeMessageMock.swift */, 9F6F3C7526ADFC37005BD9C6 /* KeyStorageMock.swift */, 9F6F3C7C26ADFC60005BD9C6 /* ContactsServiceMock.swift */, ); diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 3a5d6a27f..2417a64f9 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -11,6 +11,8 @@ enum CoreError: Error, Equatable { case notReady(String) case format(String) case keyMismatch(String) + case noMDC(String) + case badMDC(String) // wrong value passed into a function case value(String) @@ -21,6 +23,8 @@ enum CoreError: Error, Equatable { switch errorType { case "format": self = .format(coreError.error.message) case "key_mismatch": self = .keyMismatch(coreError.error.message) + case "no_mdc": self = .noMDC(coreError.error.message) + case "bad_mdc": self = .badMDC(coreError.error.message) default: return nil } } @@ -30,7 +34,7 @@ protocol KeyDecrypter { func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey } -final class Core: KeyDecrypter, CoreComposeMessageType { +final class Core: KeyDecrypter, CoreComposeMessageType, CoreMessageType { static let shared = Core() private var jsEndpointListener: JSValue? diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index b1fbbd708..aca7698e6 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -8,6 +8,7 @@ import Foundation import Promises +import FlowCryptCommon // MARK: - MessageAttachment struct MessageAttachment: FileType { @@ -38,6 +39,11 @@ extension ProcessedMessage { ) } +protocol CoreMessageType { + func parseDecryptMsg(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?, isEmail: Bool) throws -> CoreRes.ParseDecryptMsg + func decryptFile(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?) throws -> CoreRes.DecryptFile +} + // MARK: - MessageService enum MessageServiceError: Error { case missedPassPhrase(_ rawMimeData: Data) @@ -47,15 +53,19 @@ enum MessageServiceError: Error { } final class MessageService { + private enum Constants { + static let encryptedAttachmentExtension = "pgp" + } + private let messageProvider: MessageProvider private let keyService: KeyServiceType private let passPhraseService: PassPhraseServiceType - private let core: Core + private let core: CoreMessageType init( messageProvider: MessageProvider = MailProvider.shared.messageProvider, keyService: KeyServiceType = KeyService(), - core: Core = Core.shared, + core: CoreMessageType = Core.shared, passPhraseService: PassPhraseServiceType = PassPhraseService() ) { self.messageProvider = messageProvider @@ -90,7 +100,7 @@ final class MessageService { reject(MessageServiceError.wrongPassPhrase(rawMimeData, passPhrase)) } else { self.savePassPhrases(value: passPhrase, with: keys) - let processedMessage = self.processMessage(rawMimeData: rawMimeData, with: decrypted) + let processedMessage = try self.processMessage(rawMimeData: rawMimeData, with: decrypted, keys: keys) resolve(processedMessage) } } @@ -125,18 +135,29 @@ final class MessageService { isEmail: true ) - let processedMessage = self.processMessage(rawMimeData: rawMimeData, with: decrypted) + let processedMessage = try self.processMessage(rawMimeData: rawMimeData, with: decrypted, keys: keys) resolve(processedMessage) } } - private func processMessage(rawMimeData: Data, with decrypted: CoreRes.ParseDecryptMsg) -> ProcessedMessage { + private func processMessage(rawMimeData: Data, with decrypted: CoreRes.ParseDecryptMsg, keys: [PrvKeyInfo]) throws -> ProcessedMessage { let decryptErrBlocks = decrypted.blocks .filter { $0.decryptErr != nil } - let attachments = decrypted.blocks + let attachments = try decrypted.blocks .filter(\.isAttachmentBlock) - .map(MessageAttachment.init) + .map { block -> MessageAttachment in + var name = block.attMeta?.name ?? "Attachment" + let size = block.attMeta?.length ?? 0 + var data = block.attMeta?.data ?? Data() + + if name.fileExtension == Constants.encryptedAttachmentExtension { + data = (try core.decryptFile(encrypted: data, keys: keys, msgPwd: nil).content) + name = name.dropExtension() + } + + return MessageAttachment(name: name, size: size, data: data) + } let messageType: ProcessedMessage.MessageType let text: String diff --git a/FlowCryptAppTests/Core/FlowCryptCoreTests.swift b/FlowCryptAppTests/Core/FlowCryptCoreTests.swift index 1f191b190..df41b2a2c 100644 --- a/FlowCryptAppTests/Core/FlowCryptCoreTests.swift +++ b/FlowCryptAppTests/Core/FlowCryptCoreTests.swift @@ -128,6 +128,43 @@ class FlowCryptCoreTests: XCTestCase { XCTAssertNotNil(mime.range(of: "Subject: \(msg.subject)")) // has mime Subject header XCTAssertNil(mime.range(of: "In-Reply-To")) // Not a reply } + + func testComposeEmailInlineWithAttachment() throws { + + let initialFileName = "data.txt" + let urlPath = URL(fileURLWithPath: Bundle(for: type(of: self)) + .path(forResource: "data", ofType: "txt")!) + let fileData = try! Data(contentsOf: urlPath, options: .dataReadingMapped) + + let attachment = SendableMsg.Attachment( + name: initialFileName, type: "text/plain", + base64: fileData.base64EncodedString() + ) + + let msg = SendableMsg( + text: "this is the message", + to: ["email@hello.com"], cc: [], bcc: [], + from: "sender@hello.com", + subject: "subj", replyToMimeMsg: nil, + atts: [attachment], pubKeys: nil + ) + let expectation = XCTestExpectation() + + var mime: String = "" + core.composeEmail(msg: msg, fmt: .encryptInline, pubKeys: [TestData.k0.pub, TestData.k1.pub]) + .sinkFuture( + receiveValue: { composeEmailRes in + mime = String(data: composeEmailRes.mimeEncoded, encoding: .utf8)! + expectation.fulfill() + }, receiveError: {_ in } + ) + .store(in: &cancellable) + wait(for: [expectation], timeout: 3) + XCTAssertNil(mime.range(of: msg.text)) // text encrypted + XCTAssertNotNil(mime.range(of: "Content-Type: application/pgp-encrypted")) // encrypted + XCTAssertNotNil(mime.range(of: "name=\(attachment.name)")) // attachment + XCTAssertNotNil(mime.range(of: "Subject: \(msg.subject)")) // has mime Subject header + } func testEndToEnd() throws { let passphrase = "some pass phrase test" @@ -290,7 +327,51 @@ class FlowCryptCoreTests: XCTestCase { XCTAssertNotNil(message.range(of: "Missing appropriate key")) } } - + + func testDecryptEncryptedFile() throws { + // Given + let initialFileName = "data.txt" + let urlPath = URL(fileURLWithPath: Bundle(for: type(of: self)) + .path(forResource: "data", ofType: "txt")!) + let fileData = try! Data(contentsOf: urlPath, options: .dataReadingMapped) + + let passphrase = "some pass phrase test" + let email = "e2e@domain.com" + let generateKeyRes = try core.generateKey( + passphrase: passphrase, + variant: KeyVariant.curve25519, + userIds: [UserId(email: email, name: "End to end")] + ) + let k = generateKeyRes.key + let keys = [ + PrvKeyInfo( + private: k.private!, + longid: k.ids[0].longid, + passphrase: passphrase, + fingerprints: k.fingerprints + ) + ] + + // When + do { + let encrypted = try core.encryptFile( + pubKeys: [k.public], + fileData: fileData, + name: initialFileName + ) + let decrypted = try self.core.decryptFile( + encrypted: encrypted.encryptedFile, + keys: keys, + msgPwd: nil + ) + // Then + XCTAssertEqual(decrypted.name, initialFileName) + XCTAssertEqual(decrypted.content.count, fileData.count) + } catch { + XCTFail("Core file decryption should not fail") + } + } + func testException() throws { do { _ = try core.decryptKey(armoredPrv: "not really a key", passphrase: "whatnot") diff --git a/FlowCryptCommon/Extensions/StringExtensions.swift b/FlowCryptCommon/Extensions/StringExtensions.swift index 52f448087..1794d4273 100644 --- a/FlowCryptCommon/Extensions/StringExtensions.swift +++ b/FlowCryptCommon/Extensions/StringExtensions.swift @@ -45,6 +45,20 @@ public extension String { } } } + + var fileExtension: String? { + guard let fileExtension = self.split(separator: ".").last else { + return nil + } + return String(fileExtension) + } + + func dropExtension() -> String { + guard let fileExtension = self.fileExtension else { + return self + } + return self.replacingOccurrences(of: ".\(fileExtension)", with: "") + } } public extension NSAttributedString { From 21c4e2cd470f81dccfda70c718f24b438973c8b3 Mon Sep 17 00:00:00 2001 From: Personal Date: Thu, 30 Sep 2021 22:24:14 +0300 Subject: [PATCH 2/4] Removed unused protocol --- FlowCrypt/Core/Core.swift | 2 +- .../Mail Provider/Message Provider/MessageService.swift | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 2417a64f9..d156827e7 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -34,7 +34,7 @@ protocol KeyDecrypter { func decryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.DecryptKey } -final class Core: KeyDecrypter, CoreComposeMessageType, CoreMessageType { +final class Core: KeyDecrypter, CoreComposeMessageType { static let shared = Core() private var jsEndpointListener: JSValue? diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index aca7698e6..2730efd0f 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -39,11 +39,6 @@ extension ProcessedMessage { ) } -protocol CoreMessageType { - func parseDecryptMsg(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?, isEmail: Bool) throws -> CoreRes.ParseDecryptMsg - func decryptFile(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?) throws -> CoreRes.DecryptFile -} - // MARK: - MessageService enum MessageServiceError: Error { case missedPassPhrase(_ rawMimeData: Data) @@ -60,12 +55,12 @@ final class MessageService { private let messageProvider: MessageProvider private let keyService: KeyServiceType private let passPhraseService: PassPhraseServiceType - private let core: CoreMessageType + private let core: Core init( messageProvider: MessageProvider = MailProvider.shared.messageProvider, keyService: KeyServiceType = KeyService(), - core: CoreMessageType = Core.shared, + core: Core = Core.shared, passPhraseService: PassPhraseServiceType = PassPhraseService() ) { self.messageProvider = messageProvider From d951666784eda960bc50dd199d870f179c65ced6 Mon Sep 17 00:00:00 2001 From: Personal Date: Fri, 1 Oct 2021 19:19:59 +0300 Subject: [PATCH 3/4] Fixed PR comments --- .../Message Provider/MessageService.swift | 13 +++++++------ FlowCryptCommon/Extensions/StringExtensions.swift | 14 ++------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 2730efd0f..1d4c233b4 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -141,14 +141,15 @@ final class MessageService { let attachments = try decrypted.blocks .filter(\.isAttachmentBlock) - .map { block -> MessageAttachment in - var name = block.attMeta?.name ?? "Attachment" - let size = block.attMeta?.length ?? 0 - var data = block.attMeta?.data ?? Data() + .compactMap { block -> MessageAttachment? in + guard let attMeta = block.attMeta else { return nil } + var name = attMeta.name + let size = attMeta.length + var data = attMeta.data - if name.fileExtension == Constants.encryptedAttachmentExtension { + if block.type == .encryptedAtt { data = (try core.decryptFile(encrypted: data, keys: keys, msgPwd: nil).content) - name = name.dropExtension() + name = name.deletingPathExtension } return MessageAttachment(name: name, size: size, data: data) diff --git a/FlowCryptCommon/Extensions/StringExtensions.swift b/FlowCryptCommon/Extensions/StringExtensions.swift index 1794d4273..af201d9d9 100644 --- a/FlowCryptCommon/Extensions/StringExtensions.swift +++ b/FlowCryptCommon/Extensions/StringExtensions.swift @@ -46,18 +46,8 @@ public extension String { } } - var fileExtension: String? { - guard let fileExtension = self.split(separator: ".").last else { - return nil - } - return String(fileExtension) - } - - func dropExtension() -> String { - guard let fileExtension = self.fileExtension else { - return self - } - return self.replacingOccurrences(of: ".\(fileExtension)", with: "") + var deletingPathExtension: String { + return NSString(string: self).deletingPathExtension as String } } From 0d5069563e367111f64be40f95d4c6a8f8afe4af Mon Sep 17 00:00:00 2001 From: Personal Date: Sat, 2 Oct 2021 15:53:32 +0300 Subject: [PATCH 4/4] Added missing errors to Core --- FlowCrypt/Core/Core.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 640e98cbd..044d958a5 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -13,6 +13,8 @@ enum CoreError: Error, Equatable { case keyMismatch(String) case noMDC(String) case badMDC(String) + case needPassphrase(String) + case wrongPassphrase(String) // wrong value passed into a function case value(String) @@ -25,6 +27,8 @@ enum CoreError: Error, Equatable { case "key_mismatch": self = .keyMismatch(coreError.error.message) case "no_mdc": self = .noMDC(coreError.error.message) case "bad_mdc": self = .badMDC(coreError.error.message) + case "need_passphrase": self = .needPassphrase(coreError.error.message) + case "wrong_passphrase": self = .wrongPassphrase(coreError.error.message) default: return nil } }