diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index a721e0631..6921554ba 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -10,6 +10,11 @@ 04B4728D1ECE29D200B8266F /* KeyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B4728B1ECE29D200B8266F /* KeyInfo.swift */; }; 04B472951ECE29F600B8266F /* MyMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B472921ECE29F600B8266F /* MyMenuViewController.swift */; }; 04B472961ECE29F600B8266F /* SideMenuNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B472931ECE29F600B8266F /* SideMenuNavigationController.swift */; }; + 21CE25E62650070300ADFF4B /* WKDURLsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CE25E52650070300ADFF4B /* WKDURLsConstructor.swift */; }; + 21F836A02652A19A00B2448C /* WKDURLsConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CE25E52650070300ADFF4B /* WKDURLsConstructor.swift */; }; + 21F836B62652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */; }; + 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F836CB2652A38700B2448C /* ZBase32EncodingTests.swift */; }; + 21F836D32652A46E00B2448C /* WKDURLsConstructorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F836D22652A46E00B2448C /* WKDURLsConstructorTests.swift */; }; 32DCA00224982EDA88D69C6E /* AppErr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA4B11D4531B3B04D01D1 /* AppErr.swift */; }; 32DCA04CA0DAB79C39514782 /* CoreTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCAC732B988D9704658812 /* CoreTypes.swift */; }; 32DCA1414EEA727B86C337D5 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA0C3D34A69851A238E87 /* Core.swift */; }; @@ -345,6 +350,10 @@ 069059B887A8580765029BF9 /* Pods_FlowCrypt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FlowCrypt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 113F04B20ECC35FC59A81A6C /* Pods-FlowCryptTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptTests.release.xcconfig"; path = "Target Support Files/Pods-FlowCryptTests/Pods-FlowCryptTests.release.xcconfig"; sourceTree = ""; }; 11C1375F41411882DC4C9431 /* Pods-FlowCryptUIApplication.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.release.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.release.xcconfig"; sourceTree = ""; }; + 21CE25E52650070300ADFF4B /* WKDURLsConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKDURLsConstructor.swift; sourceTree = ""; }; + 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataExntensions+ZBase32Encoding.swift"; sourceTree = ""; }; + 21F836CB2652A38700B2448C /* ZBase32EncodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZBase32EncodingTests.swift; sourceTree = ""; }; + 21F836D22652A46E00B2448C /* WKDURLsConstructorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKDURLsConstructorTests.swift; sourceTree = ""; }; 27D857C43583281B45F427F8 /* Pods-FlowCryptUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUI.release.xcconfig"; path = "Target Support Files/Pods-FlowCryptUI/Pods-FlowCryptUI.release.xcconfig"; sourceTree = ""; }; 32DCA058652FD4616FB04FB6 /* SequenceExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SequenceExtensions.swift; sourceTree = ""; }; 32DCA0C3D34A69851A238E87 /* Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = ""; }; @@ -712,6 +721,40 @@ path = SideMenu; sourceTree = ""; }; + 21CE25D32650034500ADFF4B /* WKDURLs */ = { + isa = PBXGroup; + children = ( + 21CE25E52650070300ADFF4B /* WKDURLsConstructor.swift */, + ); + path = WKDURLs; + sourceTree = ""; + }; + 21F836A62652A1B700B2448C /* Functionallity */ = { + isa = PBXGroup; + children = ( + 21F836A72652A1CD00B2448C /* WKDURLs */, + ); + path = Functionallity; + sourceTree = ""; + }; + 21F836A72652A1CD00B2448C /* WKDURLs */ = { + isa = PBXGroup; + children = ( + 21F836CB2652A38700B2448C /* ZBase32EncodingTests.swift */, + 21F836D22652A46E00B2448C /* WKDURLsConstructorTests.swift */, + ); + path = WKDURLs; + sourceTree = ""; + }; + 21F836B42652A25D00B2448C /* Data */ = { + isa = PBXGroup; + children = ( + 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */, + 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */, + ); + path = Data; + sourceTree = ""; + }; 2DC3601602226D74335E18F4 /* Pods */ = { isa = PBXGroup; children = ( @@ -1197,6 +1240,7 @@ A3DAD5FC22E4574B00F2C4CD /* FlowCryptTests */ = { isa = PBXGroup; children = ( + 21F836A62652A1B700B2448C /* Functionallity */, 9F3EF32923B15C9500FA0CEF /* ImapHelperTest.swift */, 5A39F438239ECDD0001F4607 /* ExtensionTests.swift */, A3DAD5FD22E4574B00F2C4CD /* FlowCryptCoreTests.swift */, @@ -1296,6 +1340,7 @@ C132B9C61EC2DCC000763715 /* Functionality */ = { isa = PBXGroup; children = ( + 21CE25D32650034500ADFF4B /* WKDURLs */, A370EAB6238697E000685215 /* Pgp */, D29AFFEE24092F4900C1387D /* DataManager */, 9FC4114A25961CD6001180A8 /* Mail Provider */, @@ -1404,6 +1449,7 @@ D254AA5F24092A9E0041CAE0 /* Extensions */ = { isa = PBXGroup; children = ( + 21F836B42652A25D00B2448C /* Data */, 9F56BD3723438C7000A7371A /* DateFormattingExtensions.swift */, 9F716304234FC7200031645E /* LocalizationExtensions.swift */, 9F56BD3923438D3700A7371A /* StyleExtension.swift */, @@ -1417,7 +1463,6 @@ 32DCA058652FD4616FB04FB6 /* SequenceExtensions.swift */, 32DCA38E87F2B7196E0E1F1F /* CodableExntensions.swift */, 9FD22A1B230FE7D0005067A6 /* Then.swift */, - 32DCAEFF16F5D91A35791730 /* DataExtensions.swift */, D271774D24255F1100BDA9A9 /* UIColorExtensions.swift */, D2FD0F682453245E00259FF0 /* Either.swift */, ); @@ -2203,6 +2248,7 @@ D212D35E24C1AACF00035991 /* PrvKeyInfo.swift in Sources */, 9F1D5769263B540100477938 /* Logger.swift in Sources */, A3DAD5FE22E4574B00F2C4CD /* FlowCryptCoreTests.swift in Sources */, + 21F836CC2652A38700B2448C /* ZBase32EncodingTests.swift in Sources */, D2A9CA45242622F800E1D898 /* GeneralConstantsTest.swift in Sources */, A3DAD60B22E458C300F2C4CD /* DataExtensions.swift in Sources */, A3DAD60822E4588800F2C4CD /* TestData.swift in Sources */, @@ -2216,6 +2262,7 @@ 5A39F439239ECDD0001F4607 /* ExtensionTests.swift in Sources */, D212D36524C1AC4800035991 /* KeyId.swift in Sources */, 9F003DBC25EA92D000EB38C0 /* LogOutHandler.swift in Sources */, + 21F836A02652A19A00B2448C /* WKDURLsConstructor.swift in Sources */, 9F7DE8C5232029BD00F10B3E /* Core.swift in Sources */, 32DCAD6360C9EFF4FDD8EF6F /* DispatchTimeExtension.swift in Sources */, 9F7DE8C723202A0200F10B3E /* CoreHost.swift in Sources */, @@ -2223,6 +2270,7 @@ 9F003D9E25EA910B00EB38C0 /* LocalStorageTests.swift in Sources */, 32DCAF683D87EA6221F71335 /* SequenceExtensions.swift in Sources */, 9F589F11238C7DDC007FD759 /* User.swift in Sources */, + 21F836D32652A46E00B2448C /* WKDURLsConstructorTests.swift in Sources */, D2E26F6D24F263B600612AF1 /* KeyAlgo.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2247,6 +2295,7 @@ D20D3C752520AB9A00D4AA9A /* BackupService.swift in Sources */, C192421F1EC48B6900C3D251 /* SetupViewController.swift in Sources */, 9F0C3C2623194E0A00299985 /* FolderViewModel.swift in Sources */, + 21CE25E62650070300ADFF4B /* WKDURLsConstructor.swift in Sources */, 9FC411212595EA12001180A8 /* MessageSearchProvider.swift in Sources */, D27B911D24EFE806002DF0A1 /* ContactObject.swift in Sources */, 9FDF3654235A218E00614596 /* main.swift in Sources */, @@ -2446,6 +2495,7 @@ D2CDC3CE2402CDB4002B045F /* DispatchTimeExtension.swift in Sources */, 9F44971626430710003A9FE9 /* Trace.swift in Sources */, D2CDC3CD2402CCD7002B045F /* UIImageExtensions.swift in Sources */, + 21F836B62652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift in Sources */, D29AFFF6240939AE00C1387D /* Then.swift in Sources */, D2531F3423FEEF5F007E5198 /* StyleExtension.swift in Sources */, D254AA6024092AB80041CAE0 /* TapTicFeedback.swift in Sources */, diff --git a/FlowCrypt/Functionality/WKDURLs/WKDURLsConstructor.swift b/FlowCrypt/Functionality/WKDURLs/WKDURLsConstructor.swift new file mode 100644 index 000000000..120a85fa6 --- /dev/null +++ b/FlowCrypt/Functionality/WKDURLs/WKDURLsConstructor.swift @@ -0,0 +1,38 @@ +// +// WKDURLsConstructor.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 15.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import CryptoKit +import Foundation + +/// WKD - Web Key Directory, follow this link for more information https://wiki.gnupg.org/WKD + +enum WKDURLMode { + case direct + case advanced +} + +protocol WKDURLsConstructorType { + func construct(from email: String, mode: WKDURLMode) -> String? +} + +class WKDURLsConstructor: WKDURLsConstructorType { + + func construct(from email: String, mode: WKDURLMode) -> String? { + let parts = email.split(separator: "@") + if parts.count != 2 { + return nil + } + let user = String(parts[0]) + let recipientDomain = String(parts[1]).lowercased() + let hu = String(decoding: user.lowercased().data().SHA1.zBase32EncodedBytes(), as: Unicode.UTF8.self) + let directURL = "https://\(recipientDomain)/.well-known/openpgpkey/hu/\(hu)?l=\(user)" + let advancedURL = "https://openpgpkey.\(recipientDomain)/.well-known/openpgpkey/\(recipientDomain)/hu/\(hu)?l=\(user)" + + return mode == .advanced ? advancedURL : directURL + } +} diff --git a/FlowCryptCommon/Extensions/Data/DataExntensions+ZBase32Encoding.swift b/FlowCryptCommon/Extensions/Data/DataExntensions+ZBase32Encoding.swift new file mode 100644 index 000000000..5fe3cdae8 --- /dev/null +++ b/FlowCryptCommon/Extensions/Data/DataExntensions+ZBase32Encoding.swift @@ -0,0 +1,158 @@ +// +// DataExntensions+Encoding.swift +// FlowCryptCommon +// +// Created by Yevhen Kyivskyi on 17.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import Foundation + +public extension Data { + + private enum Constants { + static let alphabet = "ybndrfg8ejkmcpqxot1uwisza345h769".unicodeScalars.map { UInt8(ascii: $0) } + static let bitsInZBase32Character = 5 + static let bitsInByte = 8 + } + + func zBase32EncodedBytes() -> [UInt8] { + + let bytes = [UInt8](self) + let capacity = (bytes.count / Constants.bitsInZBase32Character) * Constants.bitsInZBase32Character + + var encoded: [UInt8] = [] + + encoded.reserveCapacity(capacity) + + var input = bytes.makeIterator() + while let firstByte = input.next() { + let secondByte = input.next() + let thirdByte = input.next() + let fourthByte = input.next() + let fifthByte = input.next() + + let firstChar = encode(firstByte: firstByte) + let secondChar = encode(firstByte: firstByte, secondByte: secondByte) + let thirdChar = encode(secondByte: secondByte) + let fourthChar = encode(secondByte: secondByte, thirdByte: thirdByte) + let fifthChar = encode(thirdByte: thirdByte, fourthByte: fourthByte) + let sixthChar = encode(fourthByte: fourthByte) + let seventhChar = encode(fourthByte: fourthByte, fifthByte: fifthByte) + let eightChar = encode(fifthByte: fifthByte) + + encoded.append(firstChar) + encoded.append(secondChar) + if let thirdChar = thirdChar { + encoded.append(thirdChar) + } + if let fourthChar = fourthChar { + encoded.append(fourthChar) + } + if let fifthChar = fifthChar { + encoded.append(fifthChar) + } + if let sixthChar = sixthChar { + encoded.append(sixthChar) + } + if let seventhChar = seventhChar { + encoded.append(seventhChar) + } + if let eightChar = eightChar { + encoded.append(eightChar) + } + } + + return encoded + } + + private func encode(firstByte: UInt8) -> UInt8 { + // First: 00000 --- + let index = firstByte >> 3 + return Constants.alphabet[Int(index)] + } + + private func encode(firstByte: UInt8, secondByte: UInt8?) -> UInt8 { + // First: -----000 + var index = (firstByte & 0b00000111) << 2 + + if let secondByte = secondByte { + // Second: 00 ------ + index |= (secondByte & 0b11000000) >> 6 + } + + return Constants.alphabet[Int(index)] + } + + private func encode(secondByte: UInt8?) -> UInt8? { + guard let secondByte = secondByte else { + return nil + } + // Second: --00000- + let index = (secondByte & 0b00111110) >> 1 + return Constants.alphabet[Int(index)] + } + + private func encode(secondByte: UInt8?, thirdByte: UInt8?) -> UInt8? { + guard let secondByte = secondByte else { + return nil + } + // Second: -------0 + var index = (secondByte & 0b00000001) << 4 + + if let thirdByte = thirdByte { + // Third: 0000---- + index |= (thirdByte & 0b11110000) >> 4 + } + + return Constants.alphabet[Int(index)] + } + + private func encode(thirdByte: UInt8?, fourthByte: UInt8?) -> UInt8? { + guard let thirdByte = thirdByte else { + return nil + } + // Third:----0000 + var index = (thirdByte & 0b00001111) << 1 + + if let fourthByte = fourthByte { + // Fourth: 0------- + index |= (fourthByte & 0b10000000) >> 7 + } + + return Constants.alphabet[Int(index)] + } + + private func encode(fourthByte: UInt8?) -> UInt8? { + guard let fourthByte = fourthByte else { + return nil + } + // Fourth: -00000-- + let index = (fourthByte & 0b01111100) >> 2 + return Constants.alphabet[Int(index)] + } + + private func encode(fourthByte: UInt8?, fifthByte: UInt8?) -> UInt8? { + guard let fourthByte = fourthByte else { + return nil + } + // Fourth: ------00 + var index = (fourthByte & 0b00000011) << 3 + + if let fifthByte = fifthByte { + // Fifth: 000----- + index |= (fifthByte & 0b11100000) >> 5 + } + + return Constants.alphabet[Int(index)] + } + + private func encode(fifthByte: UInt8?) -> UInt8? { + guard let fifthByte = fifthByte else { + return nil + } + // // Fifth: ---00000 + let index = fifthByte & 0b00011111 + return Constants.alphabet[Int(index)] + } +} diff --git a/FlowCryptCommon/Extensions/DataExtensions.swift b/FlowCryptCommon/Extensions/Data/DataExtensions.swift similarity index 100% rename from FlowCryptCommon/Extensions/DataExtensions.swift rename to FlowCryptCommon/Extensions/Data/DataExtensions.swift diff --git a/FlowCryptTests/Functionallity/WKDURLs/WKDURLsConstructorTests.swift b/FlowCryptTests/Functionallity/WKDURLs/WKDURLsConstructorTests.swift new file mode 100644 index 000000000..ec20e582a --- /dev/null +++ b/FlowCryptTests/Functionallity/WKDURLs/WKDURLsConstructorTests.swift @@ -0,0 +1,77 @@ +// +// WKDURLsTests.swift +// FlowCryptTests +// +// Created by Yevhen Kyivskyi on 17.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import XCTest + +class WKDURLsTests: XCTestCase { + + var constructor: WKDURLsConstructorType! + + override func setUp() { + constructor = WKDURLsConstructor() + } + + override func tearDown() { + constructor = nil + } + + func test_direct_mode_lowercased_construct_URL_success() { + let inputEmail = "recipient.hello@example.com" + let expectedURL = "https://example.com/.well-known/openpgpkey/hu/1sbjrcaf8m3zckmmuej93nx61yn1sttg?l=recipient.hello" + + let constructedURL = constructor.construct(from: inputEmail, mode: .direct) + + XCTAssert(constructedURL == expectedURL) + } + + func test_advanced_mode_lowercased_construct_URL_success() { + let inputEmail = "recipient.hello@example.com" + let expectedURL = "https://openpgpkey.example.com/.well-known/openpgpkey/example.com/hu/1sbjrcaf8m3zckmmuej93nx61yn1sttg?l=recipient.hello" + + let constructedURL = constructor.construct(from: inputEmail, mode: .advanced) + + XCTAssert(constructedURL == expectedURL) + } + + func test_direct_mode_uppercased_construct_URL_success() { + let inputEmail = "UPPER@EXAMPLE.COM" + let expectedURL = "https://example.com/.well-known/openpgpkey/hu/awhcnhf7a4ax8qha5u1rwymkfaswmjz8?l=UPPER" + + let constructedURL = constructor.construct(from: inputEmail, mode: .direct) + + XCTAssert(constructedURL == expectedURL) + } + + func test_advanced_mode_uppercased_construct_URL_success() { + let inputEmail = "UPPER@EXAMPLE.COM" + let expectedURL = "https://openpgpkey.example.com/.well-known/openpgpkey/example.com/hu/awhcnhf7a4ax8qha5u1rwymkfaswmjz8?l=UPPER" + + let constructedURL = constructor.construct(from: inputEmail, mode: .advanced) + + XCTAssert(constructedURL == expectedURL) + } + + func test_construct_URL_failure() { + var inputEmail = "examplemail.com" + var constructedURL = constructor.construct(from: inputEmail, mode: .advanced) + + XCTAssertNil(constructedURL) + + inputEmail = "example@" + + constructedURL = constructor.construct(from: inputEmail, mode: .advanced) + + XCTAssertNil(constructedURL) + + inputEmail = "@mail.com" + + constructedURL = constructor.construct(from: inputEmail, mode: .advanced) + + XCTAssertNil(constructedURL) + } +} diff --git a/FlowCryptTests/Functionallity/WKDURLs/ZBase32EncodingTests.swift b/FlowCryptTests/Functionallity/WKDURLs/ZBase32EncodingTests.swift new file mode 100644 index 000000000..8c35f4510 --- /dev/null +++ b/FlowCryptTests/Functionallity/WKDURLs/ZBase32EncodingTests.swift @@ -0,0 +1,30 @@ +// +// ZBase32EncodingTests.swift +// FlowCryptTests +// +// Created by Yevhen Kyivskyi on 17.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import XCTest + +class ZBase32EncodingTests: XCTestCase { + + func testStringEncoding() throws { + let inputString = "example@email.com" + let encodedString = "cihgn5mopt1wy3mpcfwsamudp7so" + + XCTAssert( + String(decoding: inputString.data().zBase32EncodedBytes(), as: Unicode.UTF8.self) == encodedString + ) + } + + func testEncryptedStringEncoding() throws { + let inputString = "example@email.com" + let encodedString = "8dkp15twcw7feu1i8em784qtw91y3cs7" + print(String(decoding: inputString.data().SHA1.zBase32EncodedBytes(), as: Unicode.UTF8.self)) + XCTAssert( + String(decoding: inputString.data().SHA1.zBase32EncodedBytes(), as: Unicode.UTF8.self) == encodedString + ) + } +}