Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
MailTest.swift 19.26 KiB
//
//  MailTest.swift
//  
//
//  Created by Oliver Wiese on 31.10.18.
//

import XCTest

/*
 Test cases:
 
 parse incoming mails:
 MUA = {Letterbox, AppleMail, iOSMail, Thunderbird (+ Enigmail) [DONE], K9 (+ OKC)(, WebMail)}
 MUA x EncState x SigState (x Attachment)
 
 parse pgp mails:
    * inline pgp DONE
    * mime pgp DONE
 
 parse special mails:
    * public key import (attachment, inline)
    * secret key import (attachment, inline)

 
 parse mail compontens:
    * header (to, cc, bcc, subject, date etc.) DONE
    * body DONE
    * attachments
 
What about errors and special cases?
    * mixed encState/sigState in mail
    * html mail
    * attachments
    * remote content
    * java script
 
 create Mails: -> Export as eml (in Message builder)?
    * EncState x SigState -> is correct?
    * mixed receivers (plain, enc) -> Text if matching is correct DONE
    * attach public key
    * export secret key
 
 TODOS:
 What about input validation e.g. addr: b@example
 */

@testable import enzevalos_iphone
class MailTest: XCTestCase {
    let datahandler = DataHandler.handler
    let mailHandler = AppDelegate.getAppDelegate().mailHandler
    let pgp = SwiftPGP()
    let userAdr = "bob@enzevalos.de"
    let userName = "bob"
    var user: MCOAddress =  MCOAddress.init(mailbox: "bob@enzevalos.de")
    var userKeyID: String = ""
    
    
    static let body = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque dapibus id diam ac volutpat. Sed quis cursus ante. Vestibulum eget gravida felis. Nullam accumsan diam quis sem ornare lacinia. Aenean risus risus, maximus quis faucibus et, maximus at nunc. Duis pharetra augue libero, et congue diam varius eget. Nullam efficitur ex purus, non accumsan tellus laoreet hendrerit. Suspendisse gravida interdum eros, eu venenatis ante suscipit nec. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent pellentesque cursus sem, non ornare nunc commodo vel. Praesent sed magna at ligula ultricies sagittis malesuada non est. Nam maximus varius mauris. Etiam dignissim congue ligula eu porta. Nunc rutrum nisl id mauris efficitur ultrices. Maecenas sit amet velit ac mauris consequat sagittis at et lorem.
    """
    override func setUp() {
        super.setUp()
        datahandler.reset()
        pgp.resetKeychains()
        (user, userKeyID) = owner()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    
    
    func testSimpleMailCreation() {
        // Init
        let tos = ["to1@example.com", "to2@example.com"]
        let ccs = ["cc1@example.com"]
        let bccs = ["bcc1@example.com"]
        let subject = "subject"
        let body = "This is the body"
        let outMail = OutgoingMail(toEntrys: tos, ccEntrys: ccs, bccEntrys: bccs, subject: subject, textContent: body, htmlContent: nil)
        if let data = outMail.plainData {
            // Test parsing!
            let incMail = IncomingMail(rawData: data, uID: 0, folderPath: "INBOX", flags: MCOMessageFlag.init(rawValue: 0))
            if let mail = incMail.store(keyRecord: nil){
                XCTAssertTrue(MailTest.compareAdrs(adrs1: tos, adrs2: mail.getReceivers()))
                XCTAssertTrue(MailTest.compareAdrs(adrs1: ccs, adrs2: mail.getCCs()))
                XCTAssertTrue(mail.getBCCs().count == 0)
                XCTAssertEqual(subject, mail.subject)
                XCTAssertEqual(body, mail.body)
                XCTAssertFalse(mail.isSecure)
            }
            else {
                XCTFail()
            }
        }
        else {
            XCTFail()
        }
    }
    
    
    func testFormatMail() {
        // Init
        let tos = ["to1@example.com", "to2@example.com"]
        let ccs = ["cc1@example.com"]
        let bccs = ["bcc1@example.com"]
        let subject = "subject"
        var body = """
        Another at:
        
        https://www.fu-berlin.de
        
        Found that one because I was submitting the same URL from a phishing email.
        
        See: mi.fu-berlin.de
        For host name format of Azure static web sites.
        https certs will be valid – Microsoft signed.
        """
        body = body.replacingOccurrences(of: "\n", with: "\r\n")
        let outMail = OutgoingMail(toEntrys: tos, ccEntrys: ccs, bccEntrys: bccs, subject: subject, textContent: body, htmlContent: nil)
        if let data = outMail.plainData {
            // Test parsing!
            let incMail = IncomingMail(rawData: data, uID: 0, folderPath: "INBOX", flags: MCOMessageFlag.init(rawValue: 0))
            if let mail = incMail.store(keyRecord: nil){
                XCTAssertEqual(subject, mail.subject)
                XCTAssertEqual(body, mail.body)
            }
            else {
                XCTFail()
            }
        }
        else {
            XCTFail()
        }
    }
    
    func testSecureMailCreation() {
        let encAdr = "enc@example.com"
        let subject = "subject"
        let body = "body"
        _ = createPGPUser(adr: encAdr, name: encAdr)
        let outMail = OutgoingMail(toEntrys: [encAdr], ccEntrys: [], bccEntrys: [], subject: subject, textContent: body, htmlContent: nil)
        if let data = outMail.pgpData{
            let incMail = IncomingMail(rawData: data, uID: 1, folderPath: "INBOX", flags: MCOMessageFlag.init(rawValue: 0))
            if let mail = incMail.store(keyRecord: nil) {
                XCTAssertEqual(body, mail.body)
                XCTAssertTrue(mail.isSecure)
            }
            else {
                XCTFail()
            }
        }
        else {
            XCTFail()
        }
    }
    
    func testMixedMailCreation() {
        let encAdr = "enc@example.com"
        let plainAdr = "plain@example.com"
        let subject = "subject"
        let body = "body"
        _ = createPGPUser(adr: encAdr, name: encAdr)
        let outMail = OutgoingMail(toEntrys: [plainAdr, encAdr], ccEntrys: [], bccEntrys: [], subject: subject, textContent: body, htmlContent: nil)
        if let data = outMail.pgpData {
            let incMail = IncomingMail(rawData: data, uID: 2, folderPath: "INBOX", flags: MCOMessageFlag.init(rawValue: 0))
            if let mail = incMail.store(keyRecord: nil){
                XCTAssertEqual(body, mail.body)
                XCTAssertTrue(mail.isSecure)
                XCTAssertTrue(MailTest.compareAdrs(adrs1: [encAdr, plainAdr], adrs2: mail.getReceivers()))
            }
            else {
                XCTFail()
            }
        }
        else {
            XCTFail()
        }
        if let data = outMail.plainData {
            let incMail = IncomingMail(rawData: data, uID: 3, folderPath: "INBOX", flags: MCOMessageFlag.init(rawValue: 0))

            if let mail = incMail.store(keyRecord: nil) {
                XCTAssertEqual(body, mail.body)
                XCTAssertFalse(mail.isSecure)
                XCTAssertTrue(MailTest.compareAdrs(adrs1: [plainAdr, encAdr], adrs2: mail.getReceivers()))
            }
            else {
                XCTFail()
            }
        }
        else {
            XCTFail()
        }
    }
    
    func testImportSecretKeyWithOutPWNoEnc(){
        receiveImportKeyMail(file: "BobWithoutPW", keyID: "6F60DF88E48ECBDA", pw: nil, encMail: false)
    }
    
    func testImportSecretKeyWithOutPWNoEncAttached(){
        receiveImportKeyMail(file: "BobWithoutPW", keyID: "6F60DF88E48ECBDA", pw: nil, encMail: false, attached: true)
    }
    
    func testImportSecretKeyWithOutPWEnc(){
        receiveImportKeyMail(file: "BobWithoutPW", keyID: "6F60DF88E48ECBDA", pw: nil, encMail: true)
    }
    
    func testImportSecretKeyWithPWNoEnc(){
        receiveImportKeyMail(file: "BobPWTEST1234", keyID: "15E26A88438FC2DF", pw: "TEST1234", encMail: false)
    }
    
    func testImportSecretKeyWithPWEnc(){
        receiveImportKeyMail(file: "BobPWTEST1234", keyID: "15E26A88438FC2DF", pw: "TEST1234", encMail: true)
    }
    
    func receiveImportKeyMail(file: String, keyID: String, pw: String?, encMail: Bool, attached: Bool = false){
        let bundle = Bundle(for: type(of: self))
        if let url = bundle.url(forResource: file, withExtension: "asc"), let keyData = try? Data(contentsOf: url), let body = String(data: keyData, encoding: .utf8) {
            var mail = OutgoingMail(toEntrys: [userAdr], ccEntrys: [], bccEntrys: [], subject: "New secret key", textContent: body, htmlContent: nil)
            if attached, let attachment = MCOAttachment(data: keyData, filename: "secretKey.asc") {
                attachment.mimeType = ""
                attachment.charset = "UTF-8"
                mail = OutgoingMail(toEntrys: [userAdr], ccEntrys: [], bccEntrys: [], subject: "New attached secret key", textContent: MailTest.body, htmlContent: nil, textparts: 0, sendEncryptedIfPossible: false, attachments: [attachment])
            }
            var data = mail.plainData
            if encMail {
                data = mail.pgpData
            }
            if let data = data {
                let incMail = IncomingMail(rawData: data, uID: 0, folderPath: "Inbox", flags: MCOMessageFlag(rawValue: 0))
                XCTAssertTrue(incMail.hasSecretKeys, "No secret keys in IncommingMail")
                if let storedMail = incMail.store(keyRecord: nil) {
                    XCTAssertTrue(storedMail.secretKey != nil, "No secret key in PersitentMail")
                    // When parsing a mail, we do not import the key...
                    let newSK = datahandler.findSecretKey(keyID: keyID)
                    XCTAssertNil(newSK, "New secret key should not exist")
                    
                    // ... only when calling the processSecretKey we import the key.
                    // ProcessSecretKey should require a user interaction, e.g. entering the password.
                    if let imported = try? storedMail.processSecretKey(pw: pw) {
                        XCTAssertTrue(imported, "Couldn't import key")
                        if let newSK = datahandler.findSecretKey(keyID: keyID) {
                            XCTAssertEqual(newSK.keyID, keyID)
                        }else {
                            XCTFail("New secret key should exist")
                        }
                    }
                    else {
                        XCTFail("Could not import secret key")
                    }
                }
                else {
                    XCTFail("No PersitentMail")
                }
            }
            else {
                XCTFail("No outgoing mail data")
            }
        }
        else {
            XCTFail("No key")
        }
    }
    
    func testThunderbirdPlainMail() {
        testMailAliceToBob(name: "plainThunderbird", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.NoSignature)
    }
    func testThunderbirdSecureMail(){
        testSecureMail(name: "enc+signedThunderbird")
    }
    func testThunderBirdSecureInlineMail() {
        testSecureMail(name: "enc+signedInlineThunderbird")
    }
    func testThunderbirdEncMail(){
        testMailAliceToBob(name: "encThunderbird", isSecure: false, encState: EncryptionState.ValidedEncryptedWithCurrentKey, sigState: SignatureState.NoSignature)
    }
    func testThunderbirdEncInlineMail(){
        testMailAliceToBob(name: "encInlineThunderbird", isSecure: false, encState: EncryptionState.ValidedEncryptedWithCurrentKey, sigState: SignatureState.NoSignature)
    }
    func testThunderbirdSigedInlineMail() {
        testMailAliceToBob(name: "signedInlineThunderbird", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.ValidSignature)
    }
    func testThunderbirdSigedMail() {
        testMailAliceToBob(name: "signedThunderbird", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.ValidSignature)
    }
    
    func testMacPlainMail() {
        testMailAliceToBob(name: "PlainMailFromMac", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.NoSignature)
    }
    func testMacSecureMail(){
        testSecureMail(name: "signedEncMailFromApple", accountVersion: 1)
    }
    func testMacEncMail(){
        testMailAliceToBob(name: "EncMailFromMac", isSecure: false, encState: EncryptionState.ValidedEncryptedWithCurrentKey, sigState: SignatureState.NoSignature)
    }
    func testMacSigedMail() {
        testMailAliceToBob(name: "SignedMailFromMac", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.ValidSignature)
    }
    
     func testK9SigedInlineMail() {
        testMailAliceToBob(name: "signinlineK9", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.ValidSignature, accountVersion : 3)
    }
    func testK9SigedMail() {
        testMailAliceToBob(name: "signK9", isSecure: false, encState: EncryptionState.NoEncryption, sigState: SignatureState.ValidSignature, accountVersion :  3)
    }
    func testK9SecureMail(){
        testSecureMail(name: "signencK9", accountVersion: 3)
    }
    func testK9SecureInlineMail() {
        testSecureMail(name: "signencinlineK9", accountVersion:  3)
    }
    
    func testSecureMail(name: String, accountVersion: Int  = 0) {
        testMailAliceToBob(name: name, isSecure: true, encState: nil, sigState: nil, accountVersion: accountVersion)
    }
    
    func testMailAliceToBob(name: String, isSecure: Bool, encState: EncryptionState? = nil, sigState: SignatureState? = nil, accountVersion : Int = 0) {
        testMailAliceToBob(pkExists: true, name: name, isSecure: isSecure, encState: encState, sigState: sigState,  accountVersion : accountVersion)
        if accountVersion == 0  {
            //tearDown()
            //setUp()
            //testMailAliceToBob(pkExists: false, name: name, isSecure: isSecure, encState: encState, sigState: sigState, accountVersion : accountVersion)
        }
    }
    
    func testMailAliceToBob(pkExists: Bool, name: String, isSecure: Bool, encState: EncryptionState? = nil, sigState: SignatureState? = nil, accountVersion : Int = 0) {
        let mailData = MailTest.loadMail(name: name )
        var (alice, _) = addAliceAndBob(addAlice: pkExists)
        if accountVersion == 1 {
            (alice, _) = addAliceAndBobLetterbox(addAlice: pkExists)
        }
        if accountVersion == 3 
        {
          (alice, _) = addAliceAndBobV3(addAlice:pkExists)
        }
        let incMail = IncomingMail(rawData: mailData, uID: 4, folderPath: "INBOX", flags: MCOMessageFlag.init(rawValue: 0))
        if let mail = incMail.store(keyRecord: nil) {
            XCTAssertEqual(mail.isSecure, isSecure)
            if let sig = sigState, sig == .ValidSignature{
                XCTAssertEqual(mail.signedKey?.keyID, alice)
                XCTAssertEqual(mail.keyID, alice)
            }
            if isSecure{
                XCTAssertEqual(mail.signedKey?.keyID, alice)
                XCTAssertEqual(mail.keyID, alice)
            }
            if let encState = encState {
                XCTAssertEqual(mail.encState, encState)
            }
            if let sigState = sigState {
                XCTAssertEqual(mail.sigState, sigState)
            }
            if let body = mail.body {
                XCTAssertEqual(body.removeNewLines(), MailTest.body.removeNewLines())
            }
            else {
                XCTFail("No body!")
            }
        }
        else {
            XCTFail()
        }
    }
    
    func addAliceAndBob(addAlice: Bool) -> (alice: String, bob: String){
        let aliceKeyId = importKey(file: "alicePublic", isSecretKey: false)
        if addAlice {
            _ = datahandler.newPublicKey(keyID: aliceKeyId, cryptoType: .PGP, adr: "alice@enzevalos.de", autocrypt: true, transferType: nil)
        }
        let bobKeyId = importKey(file: "bobSecret", isSecretKey: true)
        _ = datahandler.newSecretKey(keyID: bobKeyId, addPk: true)
        return (aliceKeyId, bobKeyId)
    }
    
    func addAliceAndBobLetterbox(addAlice: Bool) -> (alice: String, bob: String){
        let aliceKeyId = importKey(file: "Alice Letterbox (439EE43C) – Public", isSecretKey: false)
        if addAlice {
            _ = datahandler.newPublicKey(keyID: aliceKeyId, cryptoType: .PGP, adr: "alice@letterbox-app.org", autocrypt: false, transferType: nil)
        }
        let bobKeyId = importKey(file: "Bob Letterbox (0B6CD0A0) – Secret", isSecretKey: true)
        _ = datahandler.newSecretKey(keyID: bobKeyId, addPk: true)
        return (aliceKeyId, bobKeyId)
    }
 
    func addAliceAndBobV3(addAlice: Bool) -> (alice: String, bob: String){
        let aliceKeyId = importKey(file: "Alice.v3.pub", isSecretKey: false)
        if addAlice {
            _ = datahandler.newPublicKey(keyID: aliceKeyId, cryptoType: .PGP, adr: "alice@letterbox-app.org", autocrypt: false, transferType: nil)
        }
        let bobKeyId = importKey(file: "Bob Letterbox (0B6CD0A0) – Secret", isSecretKey: true)
        _ = datahandler.newSecretKey(keyID: bobKeyId, addPk: true)
        return (aliceKeyId, bobKeyId)
    }  
    
    static func compareAdrs(adrs1: [String], adrs2: [Mail_Address]) -> Bool{
        for adr in adrs1 {
            var found = false
            for adr2 in adrs2 {
                if adr == adr2.address {
                    found = true
                }
            }
            if !found {
                return false
            }
        }
        
        for adr in adrs2 {
            var found = false
            for adr2 in adrs1 {
                if adr.address == adr2 {
                    found = true
                }
            }
            if !found {
                return false
            }
        }
        return true
        
    }
    
    func importKey(file: String, isSecretKey: Bool) -> String{
        let bundle = Bundle(for: type(of: self))
        do {
            let keyData = try Data(contentsOf: bundle.url(forResource: file, withExtension: "asc")!)
            let ids = try pgp.importKeys(data: keyData, pw: nil, secret: isSecretKey)
            if ids.count > 0 {
                return ids.first!
            }
        } catch {
            XCTFail()
        }
        XCTFail()
        return ""
    }
    
    static func loadMail(name: String) -> Data {
        let bundle = Bundle(for: self)
        do {
            let mail = try Data(contentsOf: bundle.url(forResource: name, withExtension: "eml")!)
            return mail
        } catch {
            XCTFail()
        }
        return Data(base64Encoded: "")!
    }
    
    func createUser(adr: String = String.random().lowercased(), name: String = String.random()) -> MCOAddress {
        return MCOAddress.init(displayName: name, mailbox: adr.lowercased())
    }
    
    func createPGPUser(adr: String = String.random().lowercased(), name: String = String.random()) -> (MCOAddress, String) {
        let user = createUser(adr: adr, name: name)
        let id = pgp.generateKey(adr: user.mailbox)
        return (user, id)
    }
    
    func owner() -> (MCOAddress, String) {
        Logger.logging = false
        let (user, userid) = createPGPUser(adr: userAdr, name: userName)
        UserManager.storeUserValue(userAdr as AnyObject, attribute: Attribute.userAddr)
        UserManager.storeUserValue(userid as AnyObject, attribute: Attribute.prefSecretKeyID)
        return (user, userid)
    }
    
}