Skip to content
Merged
2 changes: 1 addition & 1 deletion FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1318,8 +1318,8 @@
isa = PBXGroup;
children = (
9F4163EC266574CB00106194 /* BackupServiceMock.swift */,
9F6F3C3B26ADFBC7005BD9C6 /* CoreComposeMessageMock.swift */,
9F6F3C6926ADFBEB005BD9C6 /* MessageGatewayMock.swift */,
9F6F3C3B26ADFBC7005BD9C6 /* CoreComposeMessageMock.swift */,
9F6F3C7526ADFC37005BD9C6 /* KeyStorageMock.swift */,
9F6F3C7C26ADFC60005BD9C6 /* ContactsServiceMock.swift */,
);
Expand Down
8 changes: 8 additions & 0 deletions FlowCrypt/Core/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ enum CoreError: Error, Equatable {
case notReady(String)
case format(String)
case keyMismatch(String)
case noMDC(String)
case badMDC(String)
case needPassphrase(String)
case wrongPassphrase(String)
// wrong value passed into a function
case value(String)

Expand All @@ -21,6 +25,10 @@ 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)
case "need_passphrase": self = .needPassphrase(coreError.error.message)
case "wrong_passphrase": self = .wrongPassphrase(coreError.error.message)
default: return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Foundation
import Promises
import FlowCryptCommon

// MARK: - MessageAttachment
struct MessageAttachment: FileType {
Expand Down Expand Up @@ -47,6 +48,10 @@ 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
Expand Down Expand Up @@ -90,7 +95,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)
}
}
Expand Down Expand Up @@ -125,18 +130,30 @@ 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)
.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 block.type == .encryptedAtt {
data = (try core.decryptFile(encrypted: data, keys: keys, msgPwd: nil).content)
name = name.deletingPathExtension
}

return MessageAttachment(name: name, size: size, data: data)
}

let messageType: ProcessedMessage.MessageType
let text: String
Expand Down
83 changes: 82 additions & 1 deletion FlowCryptAppTests/Core/FlowCryptCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions FlowCryptCommon/Extensions/StringExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public extension String {
}
}
}

var deletingPathExtension: String {
return NSString(string: self).deletingPathExtension as String
}
}

public extension NSAttributedString {
Expand Down