From 05bf6ce16404ea5df6241486111e8293f1bf89d0 Mon Sep 17 00:00:00 2001 From: Oliver Wiese <oliver.wiese@fu-berlin.de> Date: Thu, 7 Mar 2019 19:23:31 +0100 Subject: [PATCH] update mail decryption and verification --- enzevalos_iphone.xcodeproj/project.pbxproj | 4 + .../UserInterfaceState.xcuserstate | Bin 7881 -> 16887 bytes enzevalos_iphone/CryptoObject.swift | 25 +- enzevalos_iphone/Cryptography.swift | 2 +- enzevalos_iphone/DataHandler.swift | 2 +- enzevalos_iphone/MailHandler.swift | 340 +--------- enzevalos_iphone/SwiftPGP.swift | 203 ++++-- enzevalos_iphone/mail/IncomingMail.swift | 596 ++++++++++++++++++ enzevalos_iphoneTests/CoreDataTests.swift | 5 +- enzevalos_iphoneTests/CryptoTests.swift | 60 +- enzevalos_iphoneTests/MailTest.swift | 61 +- 11 files changed, 830 insertions(+), 468 deletions(-) create mode 100644 enzevalos_iphone/mail/IncomingMail.swift diff --git a/enzevalos_iphone.xcodeproj/project.pbxproj b/enzevalos_iphone.xcodeproj/project.pbxproj index 190d9b6a..651e77bc 100644 --- a/enzevalos_iphone.xcodeproj/project.pbxproj +++ b/enzevalos_iphone.xcodeproj/project.pbxproj @@ -162,6 +162,7 @@ 478154A921FF3FF400A931EC /* Invitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478154A821FF3FF400A931EC /* Invitation.swift */; }; 478154AC21FF6A9600A931EC /* Mailbot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478154AB21FF6A9600A931EC /* Mailbot.swift */; }; 478154AE2200641900A931EC /* StudyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478154AD2200641900A931EC /* StudyTest.swift */; }; + 478AF715222FD5C600AEF69E /* IncomingMail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478AF714222FD5C600AEF69E /* IncomingMail.swift */; }; 479B5977206914BE00B3944D /* CryptoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 479B5976206914BE00B3944D /* CryptoTests.swift */; }; 479B597820691BE400B3944D /* ObjectivePGP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47CEF4EC2052C3E600887CDB /* ObjectivePGP.framework */; }; 479B597920691BFB00B3944D /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 47F867E32052B49800AA832F /* libbz2.tbd */; }; @@ -363,6 +364,7 @@ 478154A821FF3FF400A931EC /* Invitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Invitation.swift; sourceTree = "<group>"; }; 478154AB21FF6A9600A931EC /* Mailbot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mailbot.swift; sourceTree = "<group>"; }; 478154AD2200641900A931EC /* StudyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyTest.swift; sourceTree = "<group>"; }; + 478AF714222FD5C600AEF69E /* IncomingMail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IncomingMail.swift; path = mail/IncomingMail.swift; sourceTree = "<group>"; }; 479B5976206914BE00B3944D /* CryptoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoTests.swift; sourceTree = "<group>"; }; 479C649521F2139B00A01071 /* support_pk.asc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = support_pk.asc; sourceTree = "<group>"; }; 479C649821F45DAF00A01071 /* HideShowPasswordTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HideShowPasswordTextField.swift; sourceTree = "<group>"; }; @@ -633,6 +635,7 @@ A1EB057B1D956838008659C1 /* MailHandler.swift */, 477548DD21F5DABE000B22A8 /* MailServerConnectionError.swift */, 47CEAC97222541B40075B7DC /* MailSession.swift */, + 478AF714222FD5C600AEF69E /* IncomingMail.swift */, ); name = mail; sourceTree = "<group>"; @@ -1588,6 +1591,7 @@ 3EC35F2D200376A1008BDF95 /* SendViewController+Invitation.swift in Sources */, 475B00341F7B9565006CDD41 /* Cryptography.swift in Sources */, A1EB057C1D956838008659C1 /* MailHandler.swift in Sources */, + 478AF715222FD5C600AEF69E /* IncomingMail.swift in Sources */, A1EB05881D956879008659C1 /* AddressHandler.swift in Sources */, 472F39701E14F75C009260FB /* DataHandler.swift in Sources */, A1C62E9A2018F716000E5273 /* OnboardingValueState.swift in Sources */, diff --git a/enzevalos_iphone.xcodeproj/project.xcworkspace/xcuserdata/Olli.xcuserdatad/UserInterfaceState.xcuserstate b/enzevalos_iphone.xcodeproj/project.xcworkspace/xcuserdata/Olli.xcuserdatad/UserInterfaceState.xcuserstate index c93b9ff53b8d6cc0260381a2db718b0bf37413fb..cc59415b1fd6ec353f8779ec0721d09d40ad4361 100644 GIT binary patch literal 16887 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 delta 5327 zcmey~%y`mvf+!2q1*VG=CDrQ%&N8qwa4-lnh%ksUs559VXfkLqXfx<A=rZUr=rb5F zm@rr|_%Zl11TX|L1Th3NgfN6MgfWCOL@?wr<TDg76fzVs6f=}Clroetlrz*bbTLd} zn94AdVLrnGh9wM38CEl_Vc5X1g<&VdUWS7VM;T5toME`caGBvQ!##%k4D}Bfo-ll4 z_|EWy;U~i{hTjZ-82&Q+W8`AwX5?YyW#nTNVH9PQVpL*OW>jHRWmIF-WHe+nVl-wn zW3*(nVzgs)VRU8mW%Oh8XAEErWDH}BVoYF6Va#C6WXxjBW6WnPU@T*-WUOVZW9(w= zX6#|?W$a^|#W<UB4&z+Ld5jC|8J9DzU|h?%m2n&6cE<gT2N*gS4>BHSJi&N|@e<>8 z#`}y97#}jeWPHW=n(-s!H^zUA|C!jCIG6;O1et`Ggqf6?RG3tm)R@$nG?+A*^qCBp z%$UrX9GRS$oS9shT$#L?0-3^@Vwn<|(wTCZN}0--%9-k!8kicHI+?ndx|!ssGtFR{ z$ux^;Hq(5j#Y`)iRxxd0+QYP$=^)cFrsGWKCx<iJG4E*zojilt!(Wa;Z8?KDg9L*l zgA{`_gA9Xg18W0Y1A7BU17`zQ1NU+Uc?JarMFw6583q*w)drph-Uhw~rUtGCfd;9D zsCWShPZ!tl%)FHR@{q(Nr^yy96Itv$U0f$`WD%dt&#D$EiIC6F&q+!w@+=O^EY3{I zN#%$akgBdWG&M2SQP4Fst<_PeHZ(CX&{423Fs-e<#K6VC&0xr2ypTbrfvbW45`!s& z8G|{4#pHu*BK6^Z!Hz{miIs~O%o!{r{DS?HvQm>vE;Dd~by+jGFsQ9yuwk%euw$@i zaA0s`aAI(75Nr@?5N;4@5N!}^5O0uJ!QjfE%HYo6!Qjc@#h}XI(;(R()gaw4sbNOL z#s;Z|-5~FYITjaZ7MCREmAIy4mgE-|`y^K8mok=2p2#K47whTb3K4cJN==-+fvdGX z+SA1~Br(Y+F)1}ifPoX{qDY2h2DOz8Q4G-xF$}Q`aSZVc2@Hu0Newa$vJG+#@(l_N ziVaE)$_*+j8B!Qh8B`fu8C)4M8L}9%8RQ#O8_XJ{8YCMe8;Tk_8@d{J8ls|dK<+3B zDN0R^7Z8KHWAZWXE|quzf#8h%a+lPk()9GyB9PmzFjO#9GE^~eGgLFwFw`>CO`a*{ ztzgu^(4f{}dX1rhp^<@`p^2fHp@pG_p_-wcp@X55A#5^>xSxzsgNXoRgZfp5ZiXHP zZiZflK8AjV2@De%CNWH&%p(xQsn?*{VA!D1pgTETz`|=9D8{BYXf^O(W|+k=n}M5Q z4udPhyeQ9<)Vz|+w9M3^P|v)S)QSe}2FV7UcmaXbysFf)#GL%%_{@Tg{Jd01L@#7m zG+9DWo^=t!Vg^eP%SgEXKLlK6SjNE3u$*B9!%Bvl=#>2A(%jU%lF%TZ$db(5)Z&uF z+=2%E27?BM$%dNVoDk#IGOU{%uO`O2h+#d0C5QzT-^j3Oa)^))3&Umx%gG@^+775; zv6Z2Qp`9U&p@t!>!MK5;!K8r!k}h|Fd|BI|21}Rw81}~ts6*1d*yIZmT>O6dDXEJX z4lr1viVK6pA#OUva5!E-8A%WKB8DR%^~hq9uoQHR;RJ))LWbiF<_j54Hdr)7O)ljz zus_Ri9wd6M!EzzPg$ApJs1V=8%siN$D-1UngcdVgWw^$0y}`P{ronbG!%YTPhT9Ex z4U!E74WW~t@FWF41o`DrgFPt5Ao=Gh!&^|X@QmR(!wZI&46hhoGrVbVXmD(BYH)6F zX>e_DTh8!~;XT6#hK~#%89p<7X>f1wXh>_wX~=2FZOChgnmm(tf~R<Ley(0(K|xNc zUPW?#N~&H#Q9g1m{m;n4Ahd*$fsv7siIKU%v%#yuyTNA(BP$~tBReBUgKtB0Lrg<z zL+Ipuot;9A{EUJOY6}?!8vGVA3N`pQL`{AmDy1*RD7lDHoKd17pdqXw66$eAX+}8) zp+$@`jIs@Z4MB?-<rx(kf*V2_LMM0Y`qhJjpHZDrBgQY-B{R7sGe0k}sB#gbI)f$5 z04+vc6a(}a^&7$)A{s&ilpSghFn|EW6ca|%IKN=u(vrlaoK$d;w3yKZR475D%^5A? z1%%-Wz_!(cty|1!4ze&_KqvwfIL0A~Md_)~LI#}17_AwM7c$y3#5V9>VYFvh4odZm z4vdZrD;S+4i&Jw_!47YTZ-{HqZ%9#g-1pb!>W>G4Fdw)vdNQajVsvNpXh>*CT*T<b z=-rUiklYXzHhHeL(&XoQ!jLS$7{nMn`MtIR`(nlr#!v>!$?vso>lwotBN)^cENF;A z%Jz)Wj4_~W&lum3-H;B>@>vj<CW2g=)Q|y6LEt>km<r1C4VgHyIQFaz$+Vynfgi0% znEXM(x}LF+ft#_2p@y-9VMVxKuwGJPacZ<*uwGGWPGU)BS!x4ALw<ukB-qOtD?<H( zLxX%4F_tq}LL6BIa%6QwVFQ1>fJk+<fsTR!sLHXhFwjvjH#4uTg@s%_V+(`Y62=C` zM#d(_=7!>il7`ZTvic>At&DAq?Tj4_<qfqBZ4K=WQ4v0=X(eHqspatkqMk0U!38;) zU@o}i1r@td;M$_ZH$NpcGAJ`W11t-(r=M{O$esy|6B#ElPHw1ZsBEZes9pjp`x&P* zsDexU8b~E89hNwmMWjN0K7;WBP#}Y|DdQr>C7>L!xS?(#<I;wDNRFsq3Ca<x7*{tm zG&Cb}#5%@}C<%HK<K~9OhNgy4xF3`qsvAJ*ngQm%9gMqCwCrKr+tAX`+7OEHin2p> z0g9eOjP*xAZaUo1v5>*FK@wDj3OeWK73b%qx@G2+q!xj~0%pTW##4|~1`{~T2rAAX zdHg~{cS95;>Mn!4bcOM1Lr+6L!b>+8Z=<;H4&&X1-UjBrhS14|V$$`G7@wj@KVy8} zFri^$LukAJzo&~UIF6vTqq0K`Bu~TK@rLmo$QR&{ecvz{=8jJwcYJ32(lDiA8p0jl z8GoTzq{sNDA-o}cYD1{9LxTfy@GvnjF{3JFVr>X-m=02z_W(sDCle2Y+9D<{Chmrr z4YL+8@iOr>%x;*|5H<ONr1|70lG>IcOwtT$%a}x&#F)gHB$y<bq#EWn%xjq6u%KaK z!=hzOGEA~eat!iJ3JmfMiyKxqtZCqBSPyDCOm>hGpDZJ*IQg%P{Nz<K!LTZZNsCE` zL1-b9cEgf|Ou7wA8$u_aladlNWHM$@TgYV8uxufdNyGAnsLA=V()AWhHVkTunJk&C zn5-LCG^}h`1>#sTSvITy!>E8Lx1z+{)bjkI?BbAs{FGvpTxVozsiR<SXaLW3Om0jb zaLYX#);5Gf<ATYD$q#P7f5W<lD3C-w7Xvp_5K{<9#=2qELa@DH8IVK-Qxx2+=!Ojl zv*MWIQ4$<e5>pB+qM1?~HbEnrDT7gRF;gZ}7E^Y^=7wzzJK>Sdl*d#Eia|wK)xuQ7 zRNSzoVJj$E33$4=`X!cSrYDx<7fqfhFPc)pR1H!MS6stX+pxW1M?)xT%Td{(8zt*8 zH8HiK=xbvtZrFveB(x+mC$l6owHQ>!Oui+dqQ%t1G?77V2~#gqA5%ZmgoeEh`x^E) z99Y6MiD@#^6b99XgAIoo4ucAl$u$ac{4UT&6Qto@Jefy9HXvR=#HlDXF}ol?Gq0rB zF}WnOEU_dt1)Kw!<}gYwWcbpM*ANNeF97j*8<-FQvxI3GC^D8d99hV;yy0j=)Z`Be zj`d8dnbv|DC|?@#7Ba1CI0lJ5P#kS!+6F59Fl&FN9ZWmHwg2&k6Ah`L0t8nL$+VBb zcp=mNhLcD&B-0_L!=M_n;dH~P2K{<?#klPkDA|B4KEZSvxng8G!|2^`22?R(t%~CX z`2B*tQ!7(b9E*}OGRsnnE;3zWx*Qr@l30@3z{tSCAjzP_pv_>)V8vk1c!%*3<9o*M zOiWCiOngiNO!Y!c;!KiE(oC{UE=<u(8BFC&l}y!4wM_L)jZDo<txQvxrZLT6n#DAS zX&%!8rbSFknD#TBWBSCb$n4DA!n}ZaA@e@w<IJa+&oG~3zRvuJ`8D%r<{vDKEPO2T zEQ&13EUGN(ESfCZEV?ZEEDkJAEG{f=EFLUgEIurLECDP*^(@&ebu2wBdsxo1{9_el zm1UJ@RbbU+bzpU3bzya5^<ed4^<nj64PXsoO=V4I&1B7H&1KDJEo3cbEoCidtz>Os zoy)qK^$_b>*2ipYY*K8xY<6tHY`JV5Y+Y<UY<+AK*jBP_V%y1fl<fuE7q)L~KiGb; z{bBpZF3hgRZpvQo!JfjN#-72R#h$~S$G(7lCHorob?h71_p+a0KgWK7{Sx~X_UG&$ zIQTh4IE*=LI6OH*IpR4IIg&Y2IW}<Y<T%f9h2s^+XHGdzWllfNbk0o9Y|dQHe9j8a zYR+2Ddd?2cO`O{~A9B9p65tZ$vgY#O^5*j83g8Oj3gN2Z>a6GL;p*d>$TgX3D%UKo z<y`ByHgIj?+QPM)YcJP+u7g~Mx!!ZLbIWsUaBFkxa_e*3aC>w6a{F@!atCvVa))zA za%XU7ap!R7aTjnGahGtHaaVA+a!=%5!+n7J4)-q}HXa2YT^=(Y3mz*T8y-6zcOFk3 zZysMBf1W^|8lKfW^|yGLc?Ecdc}008c%^t{cnx?hdF^-|c%67%cmsLEcq4eDc;k5! zd6Rild5d}5cvtam=H0`4lJ^|%ZQcjGk9eQ(zT|z)`<C}T?+4zWd`x_Td=h+$eCB+1 ze8GG%eDQpVe93$ze06*cd`)~Ud~JOFd~^5~@-60D%D0Md4d1$Yz72fa`S$T$=6lBX zo}ZszjNgpkhd+`(ia(k^mcNL<gujfxg1?HthQF1+gTITvhkqvjZ2r0Y^Z7UN@8&<j ze~SMM|2h7L0@4B+0tNy$0(Jrp0!{+n0)YZy0`US#0x1G%0<{8N0+R)%3QQN6DKJ}L ziNG>}6#}aS)(ET<*e+0iUf`C%6M>HczXbjW{1aplWD?{S<Q3!>6ciK|6cscU3=k|9 z>=B$QI8AVd;8MXAf~y4A3a%I2D0o!xoZw}_tAf`BZwfvWd@cA^@V(#{!Eb^;1b+!J z3-Jg^3dsp62q_7v3uy{z3+W1(3poh+3IzxS355!U3q=a$3RMYJ3)R#M)d_VA^$AT7 znk+O`Xu8k>p`}90g;ol!7TO`SS7^V`L7`(pCxlK3oe{bwbYJL=&_|)qLSKb`3H=fJ zC(Ix$BrGkgA*?N|D{LTaBy1vVE9@lfBJ3vYAsi|kB^)ChC!8#tDx5ByDO@C6FWe&B zCfp(1E8H(UQFyZO9O0G1>xAn!2yYVJF1%BCxA0!!i^5+-#6?s@3`LAZOhwE^EJdtE zY(>08d_??20z`sDLPWwuB1G~<>O>}ptQWZ_$|TA!DkLf*DlRH1DlMujY9(qbYA@;} z>LThU8Y!A4nk$+wS}0m9S|wT|S|{2d+9cX6+9x_$bd~6K(Osf@ME8pxtQS2jdO`HE z=vC1hqPIlvh`tnkBl=GCqv&VRuVPGMtYYk9Tw*+8d}7jKGGek~@?tt-`eKG+CSqn{ z7GjQK9%5c%K4N}i0b;RYNn$BtX=2%8xnlWZg<>sYy<)S(=8G*9TP(IhY?as=v2|j5 z#Eyzx61y&TQ|z|b1F=V9PsE;y)&CS16IT}35!Vwp5H}Jx5w{h$7k3nQ7IzhQ7f%u| z5uYf&LVTn67V&N3JH!u)9}zz$enR}J_zm$};&;XGi$9c*me7#Ume7?jkT8-kk#Lmo zlJJr6lL(Lql8Bawm57%}lt`9Hm8g<vmS~gckm!<_EiqSOzQjU_#S&{Ic1r9skvJf6 kNaDD}Nr}@EXC)p<e3AGm@gLmIVqju|(pMlfjFe;q08PCOaR2}S diff --git a/enzevalos_iphone/CryptoObject.swift b/enzevalos_iphone/CryptoObject.swift index 09a631d9..e6571a8c 100644 --- a/enzevalos_iphone/CryptoObject.swift +++ b/enzevalos_iphone/CryptoObject.swift @@ -7,18 +7,19 @@ // import Foundation -enum SignatureState { - case NoSignature - case NoPublicKey - case InvalidSignature - case ValidSignature +enum SignatureState: Int { + + case NoSignature = 0 + case NoPublicKey = 1 + case InvalidSignature = -1 + case ValidSignature = 2 } -enum EncryptionState { - case NoEncryption - case UnableToDecrypt - case ValidEncryptedWithOldKey - case ValidedEncryptedWithCurrentKey +enum EncryptionState: Int { + case NoEncryption = 0 + case UnableToDecrypt = 1 + case ValidEncryptedWithOldKey = 2 + case ValidedEncryptedWithCurrentKey = 3 } public enum CryptoScheme { @@ -89,8 +90,4 @@ public class CryptoObject { self.passcode = nil self.signedAdrs = signedAdrs } - - - - } diff --git a/enzevalos_iphone/Cryptography.swift b/enzevalos_iphone/Cryptography.swift index 26d1991f..2c47db46 100644 --- a/enzevalos_iphone/Cryptography.swift +++ b/enzevalos_iphone/Cryptography.swift @@ -20,6 +20,6 @@ public protocol Encryption { // operations on keys func encrypt(plaintext: String, ids: [String], myId: String, encryptForMyID: Bool) -> CryptoObject - func decrypt(data: Data, decryptionIDs: [String], verifyIds: [String], fromAdr: String?) -> CryptoObject + func decrypt(data: Data, attachedSignature: Data?, decKeyIDs: [String], signatureIDs: [String], fromAddr: String) -> CryptoObject } diff --git a/enzevalos_iphone/DataHandler.swift b/enzevalos_iphone/DataHandler.swift index 3e9987df..764f7fe6 100644 --- a/enzevalos_iphone/DataHandler.swift +++ b/enzevalos_iphone/DataHandler.swift @@ -829,7 +829,7 @@ class DataHandler { myfolder.maxID = mail.uid } var record = getKeyRecord(addr: mail.from.mailAddress, keyID: nil, saveRecord: false) - if let signedID = mail.signedKey?.keyID { + if let signedID = mail.signedKey?.keyID, mail.isSecure { record = getKeyRecord(addr: mail.from.mailAddress, keyID: signedID, saveRecord: false) } record.addToPersistentMails(mail) diff --git a/enzevalos_iphone/MailHandler.swift b/enzevalos_iphone/MailHandler.swift index e573b474..2122368c 100644 --- a/enzevalos_iphone/MailHandler.swift +++ b/enzevalos_iphone/MailHandler.swift @@ -512,8 +512,8 @@ class MailHandler { return } if let parser = data { - - _ = self.parseMail(parser: parser, record: record, folderPath: folderPath, uid: UInt64(message.uid), flags: message.flags) + let incomingMail = IncomingMail(rawData: parser.data(), uID: UInt64(message.uid), folderPath: folderPath, flags: message.flags) + _ = incomingMail.store(keyRecord: record) } dispatchGroup.leave() @@ -537,342 +537,6 @@ class MailHandler { } } - internal func parseMail(parser: MCOMessageParser, record: KeyRecord?, folderPath: String, uid: UInt64, flags: MCOMessageFlag) -> PersistentMail?{ - var rec: [MCOAddress] = [] - var cc: [MCOAddress] = [] - var autocrypt: Autocrypt? = nil - var newKeyIds = [String]() - var secretKey: String? = nil - var isEnc = false - var html: String - var body: String - var lineArray: [String] - var dec: CryptoObject? = nil - let header = parser.header - let msgID = header?.messageID - let userAgent = header?.userAgent - var references = [String]() - var msgParser = parser - - // 1. parse header - if header?.from == nil { - // Drops mails with no from field. Otherwise it becomes ugly with no ezcontact,fromadress etc. - return nil - } - if let refs = header?.references { - for ref in refs { - if let string = ref as? String { - references.append(string) - } - } - } - if let _ = header?.extraHeaderValue(forName: Autocrypt.AUTOCRYPTHEADER) { - autocrypt = Autocrypt(header: header!) - } - if let _ = header?.extraHeaderValue(forName: Autocrypt.SETUPMESSAGE) { - // TODO: Distinguish between other keys (future work) - return nil - } - if let to = header?.to { - for r in to { - rec.append(r as! MCOAddress) - } - } - if let c = header?.cc { - for r in c { - cc.append(r as! MCOAddress) - } - } - var attachedSignature: Data? - // 2. parse body - for a in (msgParser.attachments())! { - let at = a as! MCOAttachment - if at.mimeType == "application/pgp-encrypted" { - isEnc = true - } - if let signature = MailHandler.extractPGPSignature(attachment: at) { - attachedSignature = signature - } - if isEnc && at.mimeType == "application/octet-stream" { - msgParser = MCOMessageParser(data: at.data) - } - newKeyIds.append(contentsOf: parsePublicKeys(attachment: at)) - if let sk = parseSecretKey(attachment: at) { - secretKey = sk - } - } - body = msgParser.plainTextBodyRenderingAndStripWhitespace(false) - if isEnc { - html = msgParser.plainTextRendering() - lineArray = html.components(separatedBy: "\n") - lineArray.removeFirst(4) - body = lineArray.joined(separator: "\n") - body = body.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - body.append("\n") - dec = decryptText(body: body, from: header?.from, autocrypt: autocrypt) - if (dec?.plaintext != nil) { - msgParser = MCOMessageParser(data: dec?.decryptedData) - body = parseBody(msgParser: msgParser) - for a in (msgParser.attachments())! { - let at = a as! MCOAttachment - newKeyIds.append(contentsOf: parsePublicKeys(attachment: at)) - if let sk = parseSecretKey(attachment: at) { - secretKey = sk - } - - } - } - } - else if let attachedSig = attachedSignature { - let signedData = MailHandler.extractSignedData(data: msgParser.data()) - let pgp = SwiftPGP() - var keyIds = [String]() - if let sender = header?.from.mailbox { - if let adr = DataHandler.handler.findMailAddress(adr: sender) { - for k in adr.publicKeys { - keyIds.append(k.keyID) - } - } - dec = pgp.verify(data: signedData!, attachedSignature: attachedSig, verifyIds: keyIds, fromAdr: sender) - } - } - else { - body = parseBody(msgParser: msgParser) - if let chipher = findInlinePGP(text: msgParser.plainTextRendering()) { - dec = decryptText(body: chipher, from: header?.from, autocrypt: autocrypt) - if dec != nil { - if let text = dec?.decryptedText { - body = text.removeNewLines() - } - } - } - } - newKeyIds.append(contentsOf: findKeyString(content: body)) - if let header = header, let from = header.from, let date = header.date { - let mail = DataHandler.handler.createMail(uid, sender: from, receivers: rec, cc: cc, time: date, received: true, subject: header.subject ?? "", body: body, flags: flags, record: record, autocrypt: autocrypt, decryptedData: dec, folderPath: folderPath, secretKey: secretKey, references: references, mailagent: userAgent, messageID: msgID) - if let m = mail { - let pgp = SwiftPGP() - if let autoc = autocrypt { - if let publickeys = try? pgp.importKeys(key: autoc.key, pw: nil, isSecretKey: false, autocrypt: true) { - for pk in publickeys { - _ = DataHandler.handler.newPublicKey(keyID: pk, cryptoType: CryptoScheme.PGP, adr: from.mailbox, autocrypt: true, firstMail: mail) - } - } - } - for keyId in newKeyIds { - _ = DataHandler.handler.newPublicKey(keyID: keyId, cryptoType: CryptoScheme.PGP, adr: from.mailbox, autocrypt: false, firstMail: mail) - } - Logger.log(received: m) - } - return mail - } - return nil - } - - - func findSignedDataAndSignature(data: Data) -> (signedData: Data?, signature: Data?) { - var sig: Data? - if let parser = MCOMessageParser.init(data: data) { - for part in parser.attachments() { - if let attachment = part as? MCOAttachment, let data = MailHandler.extractPGPSignature(attachment: attachment) { - sig = data - break - } - } - } - - return (MailHandler.extractSignedData(data: data), sig) - } - - private static func matches(for regex: String, in text: String) -> [String] { - do { - let regex = try NSRegularExpression(pattern: regex) - let results = regex.matches(in: text, - range: NSRange(text.startIndex..., in: text)) - return results.map { - String(text[Range($0.range, in: text)!]) - } - } catch { - return [] - } - } - private static func findBoundary(rawMail: String) -> String? { - let boundaries = matches(for: "boundary=.*;", in: rawMail) - if let bound = boundaries.first { - let splitted = bound.split(separator: "\"") - if splitted.count == 3 { - return "--"+String(splitted[1]) as String - } - } - return nil - } - - private static func extractSignedData(data: Data) -> Data? { - if let dataString = String.init(data: data, encoding: .utf8){ - if dataString.contains("Content-Type: multipart/signed") , let boundary = findBoundary(rawMail: dataString) { - var parts = dataString.components(separatedBy: boundary).dropFirst().filter({(s: String) -> Bool in - // We drop pgp signature. - // A messages, where a signed part is signed again, are strange and verification can fail -> What about a response etc? - if s.contains("-----BEGIN PGP SIGNATURE-----") { - return false - } - return true - }) - parts = parts.map({(s: String) -> String in - // Convert strings according to RFC3156. See: https://tools.ietf.org/html/rfc3156 page 6 - var newString = s.trimmed() - newString = newString.replacingOccurrences(of: "\r\n", with: "\n") - newString = newString.replacingOccurrences(of: "\n", with: "\r\n") - // Sometimes tabs got lost?! TEST TODO - newString = newString.replacingOccurrences(of: " charset", with: "\tcharset") - //TODO: Remove -- if last symbol - if newString.hasSuffix("--") { - newString = String(newString.dropLast(2)) - } - return newString - }) - if parts.count == 0 { - return parts.first?.data(using: .utf8) - } - else { - let res = parts.joined(separator: "\r\n") - return res.data(using: .utf8) - } - } - } - return nil - } - - - private static func extractPGPSignature(attachment: MCOAttachment) -> Data? { - if attachment.mimeType == "application/pgp-signature" { - return attachment.data - } - return nil - } - - - private func parseBody(msgParser: MCOMessageParser) -> String { - if let text = msgParser.plainTextBodyRenderingAndStripWhitespace(false) { - var body = text - var c = body.first - while (c != nil && CharacterSet.whitespacesAndNewlines.contains((c?.unicodeScalars.first)!)) { - body.removeFirst() - c = body.first - } - c = body.last - while (c != nil && CharacterSet.whitespacesAndNewlines.contains((c?.unicodeScalars.first)!)) { - body.removeLast() - c = body.last - } - return body - } - return "" - } - - private func findInlinePGP(text: String) -> String? { - var range = text.range(of: "-----BEGIN PGP MESSAGE-----") - if let lower = range?.lowerBound { - range = text.range(of: "-----END PGP MESSAGE-----") - if let upper = range?.upperBound { - let retValue = String(text[lower..<upper]) - // We do not try to decrypt a previous mails. - if retValue.contains(">"){ - return nil - } - return retValue - } - } - return nil - } - - - private func findKeyString(content: String) -> [String] { - var newKey = [String]() - if content.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----") { - if let start = content.range(of: "-----BEGIN PGP PUBLIC KEY BLOCK-----") { - var end = content.range(of: "-----END PGP PUBLIC KEY BLOCK-----\n") - if end == nil { - end = content.range(of: "-----END PGP PUBLIC KEY BLOCK-----") - } - if let end = end { - let s = start.lowerBound - let e = end.upperBound - let pk = content[s..<e] - let pgp = SwiftPGP() - if let keyId = try? pgp.importKeys(key: String(pk), pw: nil, isSecretKey: false, autocrypt: false) { - newKey.append(contentsOf: keyId) - } - } - } - } - return newKey - } - - private func parsePublicKeys(attachment: MCOAttachment) -> [String] { - var newKey = [String]() - if let content = attachment.decodedString() { - newKey.append(contentsOf: findKeyString(content: content)) - } else if attachment.mimeType == "application/octet-stream", let content = String(data: attachment.data, encoding: String.Encoding.utf8), content.hasPrefix("-----BEGIN PGP PUBLIC KEY BLOCK-----") && (content.hasSuffix("-----END PGP PUBLIC KEY BLOCK-----") || content.hasSuffix("-----END PGP PUBLIC KEY BLOCK-----\n")) { - let pgp = SwiftPGP() - if let keyId = try? pgp.importKeys(key: content, pw: nil, isSecretKey: false, autocrypt: false) { - newKey.append(contentsOf: keyId) - } - } else if attachment.mimeType == "application/pgp-keys" { - let pgp = SwiftPGP() - if let keyIds = try? pgp.importKeys(data: attachment.data, pw: nil, secret: false) { - newKey.append(contentsOf: keyIds) - } - } - return newKey - } - private func parseSecretKey(attachment: MCOAttachment) -> String? { - if let content = attachment.decodedString() { - if content.contains("-----BEGIN PGP PRIVATE KEY BLOCK-----") { - if let start = content.range(of: "-----BEGIN PGP PRIVATE KEY BLOCK-----"), - let end = content.range(of: "-----END PGP PRIVATE KEY BLOCK-----") { - let s = start.lowerBound - let e = end.upperBound - let sk = String(content[s..<e]) - return sk - } - } - } - return nil - } - private func decryptText(body: String, from: MCOAddress?, autocrypt: Autocrypt?) -> CryptoObject? { - var sender: String? = nil - if let fromMCO = from { - sender = fromMCO.mailbox - } - if let data = body.data(using: String.Encoding.utf8, allowLossyConversion: true) as Data? { - let pgp = SwiftPGP() - var keyIds = [String]() - if sender != nil, let adr = DataHandler.handler.findMailAddress(adr: sender!) { - for k in adr.publicKeys { - keyIds.append(k.keyID) - } - } - if let a = autocrypt { - if let key = try? pgp.importKeys(key: a.key, pw: nil, isSecretKey: false, autocrypt: true) { - keyIds.append(contentsOf: key) - } - } - let secretkeys = DataHandler.handler.findSecretKeys() - var decIds = [String]() - for sk in secretkeys { - if let id = sk.keyID { - decIds.append(id) - } - } - - return pgp.decrypt(data: data, decryptionIDs: decIds, verifyIds: keyIds, fromAdr: sender) - } - - return nil - } - - func checkSMTP(_ completion: @escaping (MailServerConnectionError?) -> Void) { if AppDelegate.getAppDelegate().currentReachabilityStatus == .notReachable { completion(MailServerConnectionError.NoInternetconnection) diff --git a/enzevalos_iphone/SwiftPGP.swift b/enzevalos_iphone/SwiftPGP.swift index 428432c6..9ed63aff 100644 --- a/enzevalos_iphone/SwiftPGP.swift +++ b/enzevalos_iphone/SwiftPGP.swift @@ -238,6 +238,16 @@ class SwiftPGP: Encryption { } } + func importKeys(keys: [String]) -> [String] { + var keyIds = [String]() + for key in keys { + if let newIds = try? importKeys(key: key, pw: nil, isSecretKey: false, autocrypt: false) { + keyIds.append(contentsOf: newIds) + } + } + return keyIds + } + func importKeys (key: String, pw: String?, isSecretKey: Bool, autocrypt: Bool) throws -> [String]{ var keys = [Key]() if autocrypt{ @@ -387,7 +397,55 @@ class SwiftPGP: Encryption { } - func decrypt(data: Data, decryptionIDs: [String], verifyIds: [String], fromAdr: String?) -> CryptoObject{ + func verify(data: Data, attachedSignature: Data, verifyId: String, fromAdr: String) -> CryptoObject { + var sigKeyID: String? = nil + var signedAdr = [String]() + var sigState = SignatureState.NoSignature + let encState = EncryptionState.NoEncryption + let keyring = Keyring() + + if let key = loadKey(id: verifyId){ + keyring.import(keys: [key]) + } + + do{ + let keys = keyring.keys + try ObjectivePGP.verify(data, withSignature: attachedSignature, using: keys, passphraseForKey: loadPassword) + sigState = SignatureState.ValidSignature + sigKeyID = verifyId + signedAdr = vaildAddress(key: keys.first) + } catch { + let nsError = error as NSError + print(nsError) + switch nsError.code { + case 7: // no public key + sigState = SignatureState.NoPublicKey + case 8: // no signature + sigState = SignatureState.NoSignature + case 9: // unable to decrypt + sigState = SignatureState.InvalidSignature + default: + sigState = SignatureState.InvalidSignature + } + } + if !signedAdr.contains(fromAdr) && sigState == SignatureState.ValidSignature { + sigState = .InvalidSignature + } + + return CryptoObject(chiphertext: data, plaintext: nil, decryptedData: nil, sigState: sigState, encState: encState, signKey: sigKeyID, encType: .PGP, signedAdrs: signedAdr) + } + + func decrypt(mail: IncomingMail) -> CryptoObject { + let decIds = mail.decryptionKeyIDs + let data = mail.cryptoData + let sigIDs = mail.signatureKeyIDs + if let addr = mail.signatureAddr { + return decrypt(data: data, attachedSignature: nil, decKeyIDs: decIds, signatureIDs: sigIDs, fromAddr: addr) + } + return CryptoObject(chiphertext: data, plaintext: nil, decryptedData: nil, sigState: .InvalidSignature, encState: .UnableToDecrypt, signKey: nil, encType: .PGP, signedAdrs: []) //TODO: Check decryption only + } + + func decrypt(data: Data, attachedSignature: Data? = nil, decKeyIDs: [String], signatureIDs: [String], fromAddr: String) -> CryptoObject{ let prefKey = DataHandler.handler.prefSecretKey() var plaindata: Data? = nil var plaintext: String? = nil @@ -401,58 +459,59 @@ class SwiftPGP: Encryption { DECRYPTION */ // TODO: Maybe consider: try ObjectivePGP.recipientsKeyID(forMessage: ...) but currently not working... - for decID in decryptionIDs{ + for decID in decKeyIDs{ if let decKey = loadKey(id: decID){ if decID == prefID{ + keyring.import(keys: [decKey]) let (currentPlain, currentEncState) = decryptMessage(data: data, keys: [decKey], encForCurrentSK: true) if encState != EncryptionState.ValidEncryptedWithOldKey || currentEncState == EncryptionState.ValidedEncryptedWithCurrentKey{ plaindata = currentPlain encState = currentEncState + break } } - keyring.import(keys: [decKey]) } } if encState != EncryptionState.ValidedEncryptedWithCurrentKey{ + for decID in decKeyIDs { + if let decKey = loadKey(id: decID) { + keyring.import(keys: [decKey]) + } + } (plaindata, encState) = decryptMessage(data: data, keys: keyring.keys, encForCurrentSK: false) } /* VERIFICATION */ // test if message ist signed - sigState = verifyMessage(data: data, keys: keyring.keys) - - for id in verifyIds{ - if let key = loadKey(id: id){ - keyring.import(keys: [key]) - let currentState = verifyMessage(data: data, keys: keyring.keys) - if currentState == SignatureState.ValidSignature{ - sigState = currentState - sigKeyID = id - signedAdr = vaildAddress(key: key) - break - } - if currentState == SignatureState.InvalidSignature{ - sigState = currentState + let (currentSigState, signedID, signedAddr) = verifyData(toVerifyData: data, attachedSignature: attachedSignature, signatureIDs: signatureIDs, keyring: keyring) + print(currentSigState) + if currentSigState == .ValidSignature { + sigState = currentSigState + sigKeyID = signedID + signedAdr = signedAddr + } + else { + sigState = currentSigState + if let plainData = plaindata { + let (currentSigState2, signedID2, signedAddr2) = verifyData(toVerifyData: plainData, attachedSignature: attachedSignature, signatureIDs: signatureIDs, keyring: Keyring()) + if currentSigState2.rawValue > sigState.rawValue { + sigState = currentSigState2 + sigKeyID = signedID2 + signedAdr = signedAddr2 } } } if sigState == SignatureState.ValidSignature && sigKeyID == nil{ - for id in decryptionIDs{ - if let key = loadKey(id: id){ - keyring.import(keys: [key]) - let currentState = verifyMessage(data: data, keys: keyring.keys) - if currentState == SignatureState.ValidSignature{ - sigState = currentState - sigKeyID = id - signedAdr = vaildAddress(key: key) - break - } - if currentState == SignatureState.InvalidSignature{ - sigState = currentState - } - } + let currentState = verifyData(toVerifyData: data, attachedSignature: attachedSignature, signatureIDs: decKeyIDs, keyring: keyring) + if currentState.sigState == SignatureState.ValidSignature{ + sigState = currentState.sigState + sigKeyID = currentState.sigKeyID + signedAdr = currentState.signedAdr + } + if currentState.sigState == SignatureState.InvalidSignature{ + sigState = currentState.sigState } } @@ -465,18 +524,37 @@ class SwiftPGP: Encryption { else if encState == .NoEncryption{ plaintext = String(data: data, encoding: String.Encoding.utf8) } + + if sigState == .ValidSignature && !signedAdr.contains(fromAddr) { + sigState = .InvalidSignature + } return CryptoObject(chiphertext: data, plaintext: plaintext, decryptedData: plaindata, sigState: sigState, encState: encState, signKey: sigKeyID, encType: CryptoScheme.PGP, signedAdrs: signedAdr) } - - func decrypt(data: Data,decryptionId: String?, verifyIds: [String], fromAdr: String?) -> CryptoObject{ - if let decId = decryptionId{ - return decrypt(data: data, decryptionIDs: [decId], verifyIds: verifyIds, fromAdr: fromAdr) + private func verifyData(toVerifyData: Data, attachedSignature: Data?, signatureIDs: [String], keyring: Keyring) -> (sigState: SignatureState, sigKeyID: String?, signedAdr: [String]) { + var sigState = verifySignature(data: toVerifyData, attachedSignature: attachedSignature, keys: []) + print(sigState) + var sigKeyID: String? + var signedAdr: [String] = [] + for id in signatureIDs { + if let key = loadKey(id: id){ + keyring.import(keys: [key]) + let currentState = verifySignature(data: toVerifyData, attachedSignature: attachedSignature, keys: keyring.keys) + print(currentState) + if currentState == SignatureState.ValidSignature{ + sigState = currentState + sigKeyID = id + signedAdr = vaildAddress(key: key) + break + } + if currentState == SignatureState.InvalidSignature{ + sigState = currentState + } + } } - return decrypt(data: data, decryptionIDs: [String](), verifyIds: verifyIds, fromAdr: fromAdr) + return (sigState, sigKeyID, signedAdr) } - private func decryptMessage(data: Data, keys: [Key], encForCurrentSK: Bool) -> (Data?, EncryptionState){ if let dataString = String(data: data, encoding: .utf8) { do { @@ -506,28 +584,43 @@ class SwiftPGP: Encryption { return (nil, EncryptionState.NoEncryption) } - private func verifyMessage(data: Data, keys: [Key]) -> SignatureState{ - if let dataString = String(data: data, encoding: .utf8), let unarmored = try? Armor.readArmored(dataString){ - do{ - try ObjectivePGP.verify(unarmored, withSignature: nil, using: keys, passphraseForKey: loadPassword) - return SignatureState.ValidSignature - } catch { - let nsError = error as NSError - switch nsError.code { - case 7: // no public key - return SignatureState.NoPublicKey - case 8: // no signature - return SignatureState.NoSignature - case 9: // unable to decrypt - return SignatureState.InvalidSignature - default: - return SignatureState.InvalidSignature - } - } + private func verifySignature(sigString: String, attachedSignature: Data?, keys: [Key]) -> SignatureState { + if let unarmored = try? Armor.readArmored(sigString){ + return verifySignature(data: unarmored, attachedSignature: attachedSignature, keys: keys) + } + if let data = sigString.data(using: .utf8) { + return verifySignature(data: data, attachedSignature: attachedSignature, keys: keys) } return SignatureState.NoSignature } + private func verifySignature(data: Data, attachedSignature: Data?, keys: [Key]) -> SignatureState { + var sigData = data + var sigState = SignatureState.NoSignature + if let dataString = String(data: data, encoding: .utf8), let unarmored = try? Armor.readArmored(dataString){ + sigData = unarmored + } + do{ + if keys.isEmpty && attachedSignature != nil { + return SignatureState.NoPublicKey + } + try ObjectivePGP.verify(sigData, withSignature: attachedSignature, using: keys, passphraseForKey: loadPassword) + sigState = SignatureState.ValidSignature + } catch { + let nsError = error as NSError + switch nsError.code { + case 7: // no public key + sigState = SignatureState.NoPublicKey + case 8: // no signature + sigState = SignatureState.NoSignature + case 9: // unable to decrypt + sigState = SignatureState.InvalidSignature + default: + sigState = SignatureState.InvalidSignature + } + } + return sigState + } func vaildAddress(key: Key?) -> [String]{ var adrs = [String]() diff --git a/enzevalos_iphone/mail/IncomingMail.swift b/enzevalos_iphone/mail/IncomingMail.swift new file mode 100644 index 00000000..3b98565c --- /dev/null +++ b/enzevalos_iphone/mail/IncomingMail.swift @@ -0,0 +1,596 @@ +// +// IncomingMail.swift +// enzevalos_iphone +// +// Created by Oliver Wiese on 06.03.19. +// Copyright © 2019 fu-berlin. All rights reserved. +// + +import Foundation + +/** + We like to parse and store a mail. + We like to: + find public keys, secret keys + decrypt, decrypt and verify or verify a mail + */ + +enum PGPPart { + case MESSAGE, PUBLICKEY, SIGNATURE, SECRETKEY; + + var start: String { + get { + return pgpBlock(start: true) + } + } + + var end: String { + get { + return pgpBlock(start: false) + } + } + + private func pgpBlock(start: Bool) -> String{ + var type = "END" + if start { + type = "BEGIN" + } + switch self { + case .MESSAGE: + return "-----\(type) PGP MESSAGE-----" + case .PUBLICKEY: + return "-----\(type) PGP PUBLIC KEY BLOCK-----" + case .SIGNATURE: + return "-----\(type) PGP SIGNATURE-----" + case .SECRETKEY: + return "-----\(type) PGP SECRET KEY BLOCK-----" + } + } + + func findPGPPartInString(content: String) -> [String]{ + var parts = [String]() + let startString = self.start + let endString = self.end + if content.contains(startString) { + if let start = content.range(of: startString) { + var end = content.range(of: endString+"\n") + if end == nil { + end = content.range(of: endString) + } + if let end = end { + let s = start.lowerBound + let e = end.upperBound + let part = content[s..<e] + // We do not consider parts of previous mails. + if !part.contains(">"){ + parts.append(String(part)) + } + } + } + } + return parts + } +} + +enum PgpMIMEType { + case SIGNATURE, ENCRYPTED, OCTET, KEYS; + + static let allValues = [PgpMIMEType.ENCRYPTED, PgpMIMEType.KEYS, PgpMIMEType.OCTET, PgpMIMEType.SIGNATURE] + + var name: String { + get{ + switch self { + case .SIGNATURE: + return "application/pgp-signature" + case .ENCRYPTED: + return "application/pgp-encrypted" + case .OCTET: + return "application/octet-stream" + case .KEYS: + return "application/pgp-keys" + } + } + } + + func isTypeOf(attachment: MCOAttachment) -> Bool { + return attachment.mimeType == self.name + } + + static func findType(attachment: MCOAttachment) -> PgpMIMEType? { + return findType(name: attachment.mimeType) + } + + static func findType(name: String) -> PgpMIMEType? { + for type in PgpMIMEType.allValues { + if type.name == name { + return type + } + } + return nil + } +} + +class IncomingMail { + private let rawData: Data + private var msgParser: MCOMessageParser + private let firstParser: MCOMessageParser + // Header fields + private let flags: MCOMessageFlag + private let uID: UInt64 + private let folderPath: String + + private var rec: [MCOAddress] = [] + private var cc: [MCOAddress] = [] + private var from: MCOAddress? + private var fromKeyIds: [String] { + get{ + var keyIds: [String] = [] + keyIds.append(contentsOf: newAutocrypPublicKeys) + keyIds.append(contentsOf: newPublicKeys) + if let fromAdr = from?.mailbox{ + if let adr = DataHandler.handler.findMailAddress(adr: fromAdr) { + for k in adr.publicKeys { + keyIds.append(k.keyID) + } + } + } + return keyIds + } + } + private var subject: String = "" + private var date: Date = Date() + private var autocrypt: Autocrypt? = nil + private var references: [String] = [] + private var boundary: String? + private var msgID: String = "" + private var userAgent: String = "" + + + // Body info + private var secretKey: String? = nil + private var isEnc = false + private var html: String = "" + private var lineArray: [String] = [] + private var cryptoObj: CryptoObject? = nil + private var body: String = "" + private var secretKeys: [String] = [] + private var newPublicKeys: [String] = [] + private var newAutocrypPublicKeys: [String] = [] + + + // Crypto info + var cryptoData: Data { + get { + if let data = body.data(using: String.Encoding.utf8, allowLossyConversion: true) { + return data + } + return msgParser.data() + } + } + var decryptionKeyIDs: [String] { + get { + let secretkeys = DataHandler.handler.findSecretKeys() + var decIds = [String]() + for sk in secretkeys { + if let id = sk.keyID { + decIds.append(id) + } + } + return decIds + } + } + + var primaryDecryptionKeyID: String? { + let sk = DataHandler.handler.prefSecretKey() + return sk.keyID + } + + var signatureKeyIDs: [String] { + get { + return fromKeyIds + } + } + + var signatureAddr: String? { + get { + if let adr = from?.mailbox { + return adr + } + return nil + } + } + + + init(rawData: Data, uID: UInt64, folderPath: String, flags: MCOMessageFlag){ + self.rawData = rawData + self.uID = uID + self.folderPath = folderPath + self.flags = flags + self.firstParser = MCOMessageParser(data: rawData) + self.msgParser = firstParser + self.parseHeader() + self.parseBody() + } + + func store(keyRecord: KeyRecord?) -> PersistentMail? { + let sk = secretKeys.first //TODO FIX + let mail = DataHandler.handler.createMail(uID, sender: from, receivers: rec, cc: cc, time: date, received: true, subject: subject, body: body, flags: flags, record: keyRecord, autocrypt: autocrypt, decryptedData: cryptoObj, folderPath: folderPath, secretKey: sk) + if let m = mail { + let pgp = SwiftPGP() + if let autoc = autocrypt, let adr = from?.mailbox { + if let publickeys = try? pgp.importKeys(key: autoc.key, pw: nil, isSecretKey: false, autocrypt: true) { + for pk in publickeys { + _ = DataHandler.handler.newPublicKey(keyID: pk, cryptoType: CryptoScheme.PGP, adr: adr, autocrypt: true, firstMail: mail) + } + } + } + if let adr = from?.mailbox { + for keyId in newPublicKeys { + _ = DataHandler.handler.newPublicKey(keyID: keyId, cryptoType: CryptoScheme.PGP, adr: adr, autocrypt: false, firstMail: mail) + } + } + Logger.log(received: m) + } + return mail + } + + + + + + private func parseBody() { + var isEncrypted = false + var isSigned = false + var encData: Data? + var publicKeyRaw: [String] = [] + var secretKeyRaw: [String] = [] + var signaturesRaw: [String] = [] + let pgp = SwiftPGP() + for a in msgParser.attachments() { + if let attachment = a as? MCOAttachment { + if let pgpType = PgpMIMEType.findType(attachment: attachment) { + if pgpType == PgpMIMEType.ENCRYPTED { + isEncrypted = true + } + else if pgpType == PgpMIMEType.OCTET { + encData = attachment.data + } + else if pgpType == PgpMIMEType.SIGNATURE { + isSigned = true + signaturesRaw.append(contentsOf: IncomingMail.extractPGPSignature(attachment: attachment)) + } + else { + publicKeyRaw.append(contentsOf: IncomingMail.findPublicKeys(attachment: attachment)) + secretKeyRaw.append(contentsOf: IncomingMail.findSecretKeys(attachment: attachment)) + } + } + } + } + body = msgParser.plainTextBodyRenderingAndStripWhitespace(false) + publicKeyRaw.append(contentsOf:IncomingMail.extractPublicKeys(text: body)) + secretKeyRaw.append(contentsOf:IncomingMail.extractSecretKeys(text: body)) + if let encData = encData, isEncrypted { + // We drop plaintext keys. + publicKeyRaw = [] + secretKeyRaw = [] + msgParser = MCOMessageParser(data: encData) + prepareDecryption() + let cryptoObject = pgp.decrypt(mail: self) + mergeCryptoObject(newCryptoObj: cryptoObject) + msgParser = MCOMessageParser(data: cryptoObject.decryptedData) + return parseBody() + } + else if isSigned { + + let signedStrings = extractSignedParts(data: msgParser.data()) + //Signatur prüfen und dann mit Rest neu parsen??? Was wenn plainText? + var text: String? + var signedObject: CryptoObject? + if signedStrings.count == 1 { + text = signedStrings.first + } + else if signedStrings.count > 1 { + text = signedStrings.joined(separator: "\r\n") + } + if let signedData = text?.data(using: .utf8){ + for sig in signaturesRaw { + if let signature = sig.data(using: .utf8), let adr = from?.mailbox { + for id in fromKeyIds { + signedObject = pgp.verify(data: signedData, attachedSignature: signature, verifyId: id, fromAdr: adr) + if let ob = signedObject, ob.signatureState == .ValidSignature { + break + } + } + if let signedObject = signedObject { + mergeCryptoObject(newCryptoObj: signedObject) + if signedObject.signatureState != .NoSignature { + msgParser = MCOMessageParser(data: signedData) + return parseBody() + } + } + } + } + } + } + else { + plainTextBody() + let chiphers = IncomingMail.extractInlinePGP(text: body) + // We consider only the first encrypted part! TODO: improve + if let text = chiphers.first, let data = text.data(using: .utf8) { + msgParser = MCOMessageParser(data: data) + let cryptoObject = pgp.decrypt(mail: self) + mergeCryptoObject(newCryptoObj: cryptoObject) + msgParser = MCOMessageParser(data: cryptoObject.decryptedData) + return parseBody() + + } + } + newPublicKeys = pgp.importKeys(keys: publicKeyRaw) + for sk in secretKeyRaw { + if let sks = try? pgp.importKeys(key: sk, pw: nil, isSecretKey: true, autocrypt: false){ + secretKeys.append(contentsOf: sks) + } + } + } + + + private func mergeCryptoObject(newCryptoObj: CryptoObject) { + if let oldCrypto = cryptoObj { + var sigState = oldCrypto.signatureState + var encState = oldCrypto.encryptionState + var signKey = oldCrypto.signKey + var signedAddr = oldCrypto.signedAdrs + + if newCryptoObj.signatureState.rawValue > sigState.rawValue { + // TODO: What about dropped information? Users are confused? + sigState = newCryptoObj.signatureState + signKey = newCryptoObj.signKey + signedAddr = newCryptoObj.signedAdrs + } + if newCryptoObj.encryptionState.rawValue > encState.rawValue { + encState = newCryptoObj.encryptionState + } + cryptoObj = CryptoObject(chiphertext: oldCrypto.chiphertext, plaintext: newCryptoObj.plaintext, decryptedData: newCryptoObj.decryptedData, sigState: sigState, encState: encState, signKey: signKey, encType: newCryptoObj.encType, signedAdrs: signedAddr) + } + else { + cryptoObj = newCryptoObj + } + + } + + private func prepareDecryption() { + html = msgParser.plainTextRendering() + lineArray = html.components(separatedBy: "\n") + lineArray.removeFirst(4) + body = lineArray.joined(separator: "\n") + body = body.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + body.append("\n") + } + + private func plainTextBody() { + if let text = msgParser.plainTextBodyRenderingAndStripWhitespace(false) { + body = text + var c = body.first + while (c != nil && CharacterSet.whitespacesAndNewlines.contains((c?.unicodeScalars.first)!)) { + body.removeFirst() + c = body.first + } + c = body.last + while (c != nil && CharacterSet.whitespacesAndNewlines.contains((c?.unicodeScalars.first)!)) { + body.removeLast() + c = body.last + } + } + } + + private func parseHeader() { + if let header = msgParser.header { + if let refs = header.references { + for ref in refs{ + if let string = ref as? String { + references.append(string) + } + } + } + if let tos = header.to { + for r in tos{ + rec.append(r as! MCOAddress) + } + } + if let ccs = header.cc { + for r in ccs { + cc.append(r as! MCOAddress) + } + } + + if let _ = header.extraHeaderValue(forName: Autocrypt.AUTOCRYPTHEADER){ + let autocrypt = Autocrypt(header: header) + self.autocrypt = autocrypt + let pgp = SwiftPGP() + let keys = try? pgp.importKeys(key: autocrypt.key, pw: nil, isSecretKey: false, autocrypt: true) + if let keys = keys { + newAutocrypPublicKeys = keys + } + } + if let _ = header.extraHeaderValue(forName: Autocrypt.SETUPMESSAGE) { + // TODO: Distinguish between other keys (future work) + } + if let f = header.from { + from = f + } + if let sub = header.subject { + subject = sub + } + if let d = header.date { + date = d + } + if let id = header.messageID { + msgID = id + } + if let agent = header.userAgent { + userAgent = agent + } + boundary = findBoundary() + } + } + + + private func extractSignedParts(data: Data) -> [String] { + if let text = String(data: data, encoding: .utf8) { + return extractSignedParts(text: text) + } + return [String] () + } + + private func extractSignedParts(text: String) -> [String] { + var parts: [String] = [] + var boundary = IncomingMail.findBoundary(text: text) + if boundary == nil { + boundary = self.boundary + } + guard text.contains("Content-Type: multipart/signed") && boundary != nil else { + return parts + } + + parts = text.components(separatedBy: boundary!) + // If text contains a header -> we drop it + if let header = parts.first, IncomingMail.findBoundary(text: header) != nil { + parts = Array(parts.dropFirst()) + } + parts = Array(parts.filter({(s: String ) -> Bool in + // A messages, where a signed part is signed again, are strange and verification can fail -> What about a response etc? + if s.contains(PGPPart.SIGNATURE.start) { + return false + } + return true + })) + + parts = Array(parts.map({(s: String) -> String in + // Convert strings according to RFC3156. See: https://tools.ietf.org/html/rfc3156 page 6 + var newString = s.replacingOccurrences(of: "\r\n", with: "\n") + newString = newString.replacingOccurrences(of: "\n", with: "\r\n") + // Sometimes we lose tabs... TEST TODO + newString = newString.replacingOccurrences(of: " charset", with: "\tcharset") + //TODO: Remove -- if last symbol + if newString.hasSuffix("--") { + newString = String(newString.dropLast(2)) + } + else if newString.hasSuffix("--\r\n") { + newString = String(newString.dropLast(4)) + } + if newString.hasPrefix("\r\n") { + newString = String(newString.dropFirst()) + } + if newString.hasSuffix("\r\n") { + newString = String(newString.dropLast()) + } + newString = newString.trimmingCharacters(in: .whitespaces) + return newString + })) + + parts = Array(parts.filter({(s: String ) -> Bool in + if s.isEmpty { + return false + } + return true + })) + return parts + } + + + + private func findBoundary() -> String? { + if let rawString = String.init(data: rawData, encoding:.utf8) { + return IncomingMail.findBoundary(text: rawString) + } + return nil + } + + func findSignedDataAndSignature(data: Data) -> (signedTexts: [String], signature: [String]) { + var sig: [String] = [] + if let parser = MCOMessageParser.init(data: data) { + for part in parser.attachments() { + if let attachment = part as? MCOAttachment { + let data = IncomingMail.extractPGPSignature(attachment: attachment) + sig.append(contentsOf: data) + // TODO: What about more signatures? + } + } + } + + return (extractSignedParts(data: data), sig) + } + + private static func importPublicKeys(attachment: MCOAttachment) -> [String] { + var newKey = [String]() + let pgp = SwiftPGP() + if let content = attachment.decodedString() { + newKey = pgp.importKeys(keys: PGPPart.PUBLICKEY.findPGPPartInString(content: content)) + } + else if PgpMIMEType.KEYS.isTypeOf(attachment: attachment) { + if let keyIds = try? pgp.importKeys(data: attachment.data, pw: nil, secret: false) { + newKey.append(contentsOf: keyIds) + } + } + return newKey + } + + private static func findSecretKeys(attachment: MCOAttachment) -> [String] { + if let content = attachment.decodedString() { + return PGPPart.SECRETKEY.findPGPPartInString(content: content) + } + return [] + } + + private static func findPublicKeys(attachment: MCOAttachment) -> [String] { + if let content = attachment.decodedString() { + return PGPPart.PUBLICKEY.findPGPPartInString(content: content) + } + return [] + } + + private static func extractPublicKeys(text: String) -> [String] { + return PGPPart.PUBLICKEY.findPGPPartInString(content: text) + + } + + private static func extractSecretKeys(text: String) -> [String] { + return PGPPart.SECRETKEY.findPGPPartInString(content: text) + + } + + private static func extractInlinePGP(text: String) -> [String] { + return PGPPart.MESSAGE.findPGPPartInString(content: text) + } + + private static func extractPGPSignature(attachment: MCOAttachment) -> [String] { + if let content = String(data: attachment.data, encoding: .utf8) { + return PGPPart.SIGNATURE.findPGPPartInString(content: content) + } + return [] + } + + + + private static func findBoundary(text: String) -> String? { + let boundaries = matches(for: "boundary=.*;", in: text) + if let bound = boundaries.first { + let splitted = bound.split(separator: "\"") + if splitted.count == 3 { + return "--"+String(splitted[1]) as String + } + } + return nil + } + private static func matches(for regex: String, in text: String) -> [String] { + do { + let regex = try NSRegularExpression(pattern: regex) + let results = regex.matches(in: text, range: NSRange(text.startIndex..., in: text)) + return results.map { + String(text[Range($0.range, in: text)!]) + } + } catch { + return [] + } + } +} diff --git a/enzevalos_iphoneTests/CoreDataTests.swift b/enzevalos_iphoneTests/CoreDataTests.swift index 0f467544..97085653 100644 --- a/enzevalos_iphoneTests/CoreDataTests.swift +++ b/enzevalos_iphoneTests/CoreDataTests.swift @@ -264,12 +264,11 @@ class CoraDataTests: XCTestCase { XCTAssertTrue(myrecord.isSecure) XCTAssertEqual(myContact.publicKeys.count, 2) let cryptoObject2 = pgp.encrypt(plaintext: body, ids: [newKeyId], myId: keyID) - let decryptObject2 = pgp.decrypt(data: cryptoObject2.chiphertext!, decryptionIDs: [newKeyId], verifyIds: [keyID], fromAdr: sender.mailbox) - + let decryptObject2 = pgp.decrypt(data: cryptoObject2.chiphertext!, decKeyIDs: [newKeyId], signatureIDs: [keyID], fromAddr: userAdr) _ = testMail(from: sender, to: [user], cc: [], bcc: [], folder: folderName, cryptoObject: decryptObject2) let cryptoObject3 = pgp.encrypt(plaintext: body, ids: [oldID], myId: keyID) - let decryptObject3 = pgp.decrypt(data: cryptoObject3.chiphertext!, decryptionIDs: [oldID], verifyIds: [keyID], fromAdr: sender.mailbox) + let decryptObject3 = pgp.decrypt(data: cryptoObject3.chiphertext!, decKeyIDs: [oldID], signatureIDs: [keyID], fromAddr: userAdr) let oldMail = testMail(from: sender, to: [user], cc: [], bcc: [], folder: folderName, cryptoObject: decryptObject3) XCTAssertTrue((oldMail?.decryptedWithOldPrivateKey)!) diff --git a/enzevalos_iphoneTests/CryptoTests.swift b/enzevalos_iphoneTests/CryptoTests.swift index 69f8ee17..d8eabec7 100644 --- a/enzevalos_iphoneTests/CryptoTests.swift +++ b/enzevalos_iphoneTests/CryptoTests.swift @@ -360,17 +360,15 @@ class CryptoTests: XCTestCase { let signedMessage = """ -----BEGIN PGP SIGNATURE----- - iQGzBAEBCgAdFiEEJRxNIIPfYdSrprTDTbvambTX/SgFAlq5A9oACgkQTbvambTX - /SgIWwwAm1BTBjme8ogPdKcj2fyAzLjWPwttIW3nJN/tlB79TcId2sAAZK0hyhJQ - PU+j72x0IrgnJ0Vf40B9QU07RgLtdJPXXN1GWQPaz/69Wiut4T6mUusiqfE4RLiy - HJF7OYUNY6adteO1jCm0ZiZl3VW6XVomaXNuZtqXE+4U2k4WyQvaGbYMNvaaAYRf - vwYLyj/CO6++7lhYDet+A4OE5w0WcRHqrM4IRs/wF62cgBVl9cnonvl/2MTgR6DX - ewEcAeF+BmtMfVZhjEUa/zslLRTCRaxpVCq6BjHydakCTpZCJJTQcm+gVZrtQjl0 - 953zPJUfJ1/pvUextyKihT9a5itsxV2Tboq0mwD09M+hqWlHy0lQujI/hggcVvxE - DudoBwBZ4TilZyfRaSb9no1lJjxTZhwtsAvwOSPhRs3pGxkL9TS4GmqSsl8vZUBt - LBNFaFMvJfDXqmwIcW1Yl+apk7bWqY7yfTCgdRzhcqFDxdqhchNYvq4x035z4Udz - ZMarcccr - =OmRP + wsDcBAABCgAGBQJcgVq4AAoJEE272pm01/0o2Y0L/jS3UhX5ypJj1SKMVKWxHVW8CkzlHHsEtuhK + kkZs7sq9X8pqVGGeLjf+Bvz7dg5epAd76yD7M/hpHpEzAY+j4q0IjAQfkUApJIHnX303g9Xg2qC0 + KymaoHV2iD7TfkGdCpoP4lmq9DVwQWOs6ixKThO9QjNJFjZRFfI7bQlH2dKg89JVpLfoK438fm3t + pNAmcf60wYCc2SI4zGaduyiLBIHR9XG4tb9Tc82Sf3tguBNHpmTvmiJFCL7ZZNWg8lQP2kDmPkds + Ugo602abhT64VBxi4YPXHw2tNApEtniMbn03X1Z/oPMNDN3m+/0YZU6+Fc0RwJ9SBHvGxLSXH9qc + PH1tdx4Bm1z1P+oQrD9vRbTqxLM6g5vk0bG/5Esa4f1/yQWto5LH0cZXVG2Ugy2x+4fYWCumMXDS + Vj+6SjTsZpctBji7OUYA2vMNXRdtOBCrYvEoq+Nm2eATp0zba+2kUVYY5HJqDMxepdBUKI+esXDZ + wnNopEkrmkzNWaQZou1QtQ== + =p/QU -----END PGP SIGNATURE----- """ @@ -485,7 +483,7 @@ class CryptoTests: XCTestCase { XCTFail("No data") return } - let cryptoObject = pgp.decrypt(data: data, decryptionId: userKeyID, verifyIds: [], fromAdr: nil) + let cryptoObject = pgp.decrypt(data: data, decKeyIDs: [userKeyID], signatureIDs: [], fromAddr: userAdr) XCTAssert(cryptoObject.encryptionState == .NoEncryption) XCTAssert(cryptoObject.signatureState == .NoSignature) XCTAssert(cryptoObject.decryptedData == nil && cryptoObject.decryptedText == nil && cryptoObject.signKey == nil) @@ -502,7 +500,7 @@ class CryptoTests: XCTestCase { XCTFail("No chipher data") return } - let cryptoObject = pgp.decrypt(data: data, decryptionId: userKeyID, verifyIds: [], fromAdr: nil) + let cryptoObject = pgp.decrypt(data: data, attachedSignature: nil, decKeyIDs: [userKeyID], signatureIDs: [], fromAddr: userAdr) XCTAssert(cryptoObject.encryptionState == .ValidedEncryptedWithCurrentKey) XCTAssert(cryptoObject.signatureState == .NoSignature) XCTAssert(cryptoObject.plaintext == body && cryptoObject.plaintext == cryptoObject.decryptedText) @@ -520,13 +518,18 @@ class CryptoTests: XCTestCase { } // 1. case: correct signed mail - var cryptoObject = pgp.decrypt(data: signedData, decryptionId: keys.first, verifyIds: keys, fromAdr: nil) - XCTAssert(cryptoObject.encryptionState == .NoEncryption && cryptoObject.signatureState == .ValidSignature) - XCTAssert(cryptoObject.decryptedText == "only a signed mail!") - - // 2. case: manipulated mail - cryptoObject = pgp.decrypt(data: manipulatedDate, decryptionId: keys.first, verifyIds: keys, fromAdr: nil) - XCTAssert(cryptoObject.encryptionState == .NoEncryption && cryptoObject.signatureState == .InvalidSignature) + let text = "only a signed mail!" + if let data = text.data(using: .utf8) { + var cryptoObject = pgp.decrypt(data: data, attachedSignature: signedData, decKeyIDs: [userKeyID], signatureIDs: keys, fromAddr: "alice@enzevalos.de") + XCTAssert(cryptoObject.encryptionState == .NoEncryption && cryptoObject.signatureState == .ValidSignature) + XCTAssert(cryptoObject.chiperString == text) + // 2. case: manipulated mail + cryptoObject = pgp.decrypt(data: data, attachedSignature: manipulatedDate, decKeyIDs: keys, signatureIDs: keys, fromAddr: "alice@enzevalos.de") + XCTAssert(cryptoObject.encryptionState == .NoEncryption && cryptoObject.signatureState == .InvalidSignature) + } + else { + XCTFail("Can not make String to data.") + } } func testEncSignedMail() { @@ -537,30 +540,29 @@ class CryptoTests: XCTestCase { let senderPGP = SwiftPGP() let encObject = senderPGP.encrypt(plaintext: body, ids: [userKeyID], myId: senderID) XCTAssert(encObject.encryptionState == .ValidedEncryptedWithCurrentKey && encObject.signatureState == SignatureState.ValidSignature) - let falseEncObject = senderPGP.encrypt(plaintext: body, ids: [], myId: senderID) + let falseEncObject = senderPGP.encrypt(plaintext: body, ids: [id2], myId: senderID) guard let data = encObject.chiphertext, let data2 = falseEncObject.chiphertext else { XCTFail("no chipher data") return } // 1. case: signed but no public key available to verify signature - var cryptoObject = pgp.decrypt(data: data, decryptionId: userKeyID, verifyIds: [], fromAdr: nil) + var cryptoObject = pgp.decrypt(data: data, attachedSignature: nil, decKeyIDs: [userKeyID], signatureIDs: [], fromAddr: userAdr) XCTAssert(cryptoObject.encryptionState == .ValidedEncryptedWithCurrentKey) - XCTAssert(cryptoObject.signatureState == .NoPublicKey) + XCTAssert(cryptoObject.signatureState != .ValidSignature) // No PK XCTAssert(cryptoObject.plaintext == body && cryptoObject.plaintext == cryptoObject.decryptedText) - // 2. case: signed and public key available - cryptoObject = pgp.decrypt(data: data, decryptionId: userKeyID, verifyIds: [senderID, id2], fromAdr: nil) + cryptoObject = pgp.decrypt(data: data, attachedSignature: nil, decKeyIDs: [userKeyID], signatureIDs: [senderID, id2], fromAddr: senderAddress.mailbox) XCTAssert(cryptoObject.signatureState == .ValidSignature) XCTAssert(cryptoObject.signKey == senderID) XCTAssert(cryptoObject.signedAdrs.contains(senderAddress.mailbox) && cryptoObject.signedAdrs.count == 1) // 3. case: signed and check with wrong key - cryptoObject = pgp.decrypt(data: data, decryptionId: userKeyID, verifyIds: [id2], fromAdr: nil) - XCTAssert(cryptoObject.signatureState == .NoPublicKey) + cryptoObject = pgp.decrypt(data: data, decKeyIDs: [userKeyID], signatureIDs: [id2], fromAddr: senderAddress.mailbox) + XCTAssert(cryptoObject.signatureState != .ValidSignature) // 4. case: can not decrypt (wrong/missing decryption/encryption key) - cryptoObject = pgp.decrypt(data: data2, decryptionId: userKeyID, verifyIds: [senderID], fromAdr: nil) + cryptoObject = pgp.decrypt(data: data2, decKeyIDs: [userKeyID], signatureIDs: [senderID], fromAddr: senderAddress.mailbox) XCTAssert(cryptoObject.encryptionState == .UnableToDecrypt && cryptoObject.signatureState == .NoSignature) // 5. case: used old key to encrypt message @@ -572,7 +574,7 @@ class CryptoTests: XCTestCase { XCTAssertEqual(keys.count, 1) _ = datahandler.newSecretKeys(keyIds: keys, addPKs: true) XCTAssertEqual(keys.first, datahandler.prefSecretKey().keyID) - cryptoObject = pgp.decrypt(data: data, decryptionId: userKeyID, verifyIds: [senderID], fromAdr: nil) + cryptoObject = pgp.decrypt(data: data, decKeyIDs: [userKeyID], signatureIDs: [senderID], fromAddr: senderAddress.mailbox) XCTAssertEqual(keys.first, datahandler.prefSecretKey().keyID) XCTAssert(cryptoObject.encryptionState == .ValidEncryptedWithOldKey && cryptoObject.signatureState == .ValidSignature) XCTAssert(cryptoObject.decryptedText == body) diff --git a/enzevalos_iphoneTests/MailTest.swift b/enzevalos_iphoneTests/MailTest.swift index 1a48d4a3..3b4f158b 100644 --- a/enzevalos_iphoneTests/MailTest.swift +++ b/enzevalos_iphoneTests/MailTest.swift @@ -82,9 +82,10 @@ class MailTest: XCTestCase { 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 parser = MCOMessageParser(data: outMail.plainData) { + if let data = outMail.plainData { // Test parsing! - if let mail = mailHandler.parseMail(parser: parser, record: nil, folderPath: "INBOX", uid: 0, flags: MCOMessageFlag.seen){ + 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) @@ -120,9 +121,10 @@ class MailTest: XCTestCase { https certs will be valid – Microsoft signed. """ let outMail = OutgoingMail(toEntrys: tos, ccEntrys: ccs, bccEntrys: bccs, subject: subject, textContent: body, htmlContent: nil) - if let parser = MCOMessageParser(data: outMail.plainData) { + if let data = outMail.plainData { // Test parsing! - if let mail = mailHandler.parseMail(parser: parser, record: nil, folderPath: "INBOX", uid: 0, flags: MCOMessageFlag.seen){ + 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) } @@ -143,8 +145,9 @@ class MailTest: XCTestCase { let body = "body" _ = createPGPUser(adr: encAdr, name: encAdr) let outMail = OutgoingMail(toEntrys: [encAdr], ccEntrys: [], bccEntrys: [], subject: subject, textContent: body, htmlContent: nil) - if let parser = MCOMessageParser(data: outMail.pgpData){ - if let mail = mailHandler.parseMail(parser: parser, record: nil, folderPath: "INBOX", uid: 1, flags: MCOMessageFlag.seen) { + 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) } @@ -164,8 +167,9 @@ class MailTest: XCTestCase { let body = "body" _ = createPGPUser(adr: encAdr, name: encAdr) let outMail = OutgoingMail(toEntrys: [plainAdr, encAdr], ccEntrys: [], bccEntrys: [], subject: subject, textContent: body, htmlContent: nil) - if let secureParser = MCOMessageParser(data: outMail.pgpData) { - if let mail = mailHandler.parseMail(parser: secureParser, record: nil, folderPath: "INBOX", uid: 2, flags: MCOMessageFlag.seen) { + 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())) @@ -177,8 +181,10 @@ class MailTest: XCTestCase { else { XCTFail() } - if let insecureParser = MCOMessageParser(data: outMail.plainData) { - if let mail = mailHandler.parseMail(parser: insecureParser, record: nil, folderPath: "INXBO", uid: 3, flags: MCOMessageFlag.seen) { + 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())) @@ -232,23 +238,22 @@ class MailTest: XCTestCase { func testMailAliceToBob(pkExists: Bool, name: String, isSecure: Bool, encState: EncryptionState? = nil, sigState: SignatureState? = nil) { let mailData = MailTest.loadMail(name: name ) let (alice, _) = addAliceAndBob(addAlice: pkExists) - if let parser = MCOMessageParser(data: mailData) { - if let mail = mailHandler.parseMail(parser: parser, record: nil, folderPath: "INBOX", uid: 0, flags: MCOMessageFlag.seen) { - XCTAssertEqual(mail.isSecure, isSecure) - if mail.isSecure || mail.sigState == .ValidSignature{ - 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) - } - let body = mail.body - XCTAssertEqual(body.removeNewLines(), MailTest.body.removeNewLines()) - XCTAssertTrue(MailTest.compareAdrs(adrs1: ["bob@enzevalos.de"], adrs2: mail.getReceivers())) + 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 mail.isSecure || mail.sigState == .ValidSignature{ + 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) } + let body = mail.body + XCTAssertEqual(body.removeNewLines(), MailTest.body.removeNewLines()) + XCTAssertTrue(MailTest.compareAdrs(adrs1: ["bob@enzevalos.de"], adrs2: mail.getReceivers())) } else { XCTFail() @@ -258,7 +263,8 @@ class MailTest: XCTestCase { 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) + let b = datahandler.newPublicKey(keyID: aliceKeyId, cryptoType: .PGP, adr: "alice@enzevalos.de", autocrypt: true) + print(b.keyID) } let bobKeyId = importKey(file: "bobSecret", isSecretKey: true) _ = datahandler.newSecretKey(keyID: bobKeyId, addPk: true) @@ -327,6 +333,7 @@ class MailTest: XCTestCase { 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) + print(id) return (user, id) } -- GitLab