From 347fe7c5e205724423d0cda196c820c577d0a91b Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Mon, 2 Aug 2021 19:05:39 +0300 Subject: [PATCH 1/5] Handled empty or not decrypted keys while searching EKM keys; --- .../Setup/SetupInitialViewController.swift | 24 ++++++++++-- .../UIViewControllerExtensions.swift | 16 ++++++++ .../Backup Services/BackupService.swift | 2 +- .../Services/EmailKeyManagerApi.swift | 38 ++++++++++++++++--- .../Resources/en.lproj/Localizable.strings | 5 ++- 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index d8ff44735..53603b2b7 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -161,9 +161,27 @@ extension SetupInitialViewController { guard let urlString = self?.emailKeyManagerApi.getPrivateKeysUrlString() else { fatalError("Private keys URL can not be nil at this point") } - self?.showToast( - "organisational_rules_ekm_private_keys_message".localizeWithArguments(result.privateKeys.count, urlString) - ) + + switch result { + case .success(decryptedResponse: let response): + self?.showToast( + "organisational_rules_ekm_private_keys_message".localizeWithArguments(response.privateKeys.count, urlString) + ) + case .noKeys: + self?.showRetryAlert( + message: "organisational_rules_ekm_empty_private_keys_error".localized, + onRetry: { + self?.state = .fetchingKeysFromEKM + }, + onOk: { + self?.router.signOut() + } + ) + case .keysAreNotDecrypted: + self?.showAlert(message: "organisational_rules_ekm_keys_are_not_decrypted_error".localized, onOk: { + self?.router.signOut() + }) + } // todo - this is temporary, until we finish EKM integration // instead we should use the keys from EKM for setup self?.state = .searchingKeyBackupsInInbox diff --git a/FlowCrypt/Extensions/UIViewControllerExtensions.swift b/FlowCrypt/Extensions/UIViewControllerExtensions.swift index e925ac996..5fe9faa56 100644 --- a/FlowCrypt/Extensions/UIViewControllerExtensions.swift +++ b/FlowCrypt/Extensions/UIViewControllerExtensions.swift @@ -102,6 +102,22 @@ extension UIViewController { } } + func showRetryAlert( + title: String? = "Error", + message: String, + onRetry: (() -> Void)? = nil, + onOk: (() -> Void)? = nil + ) { + DispatchQueue.main.async { + self.view.hideAllToasts() + self.hideSpinner() + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Retry", style: .cancel) { _ in onRetry?() }) + alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in onOk?() }) + self.present(alert, animated: true, completion: nil) + } + } + func showSpinner(_ message: String = "loading_title".localized, isUserInteractionEnabled: Bool = false) { DispatchQueue.main.async { guard self.view.subviews.first(where: { $0 is MBProgressHUD }) == nil else { diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index cac38b64f..a33f1b61c 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -46,7 +46,7 @@ extension BackupService: BackupServiceType { } func backupToInbox(keys: [KeyDetails], for userId: UserId) -> Promise { - Promise { [weak self] (resolve, reject) -> Void in + Promise { [weak self] resolve, reject -> Void in guard let self = self else { throw AppErr.nilSelf } let isFullyEncryptedKeys = keys.map(\.isFullyDecrypted).contains(false) diff --git a/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift b/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift index 62ad54b4b..b8e0014c9 100644 --- a/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift +++ b/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift @@ -11,13 +11,20 @@ import Promises protocol EmailKeyManagerApiType { func getPrivateKeysUrlString() -> String? - func getPrivateKeys() -> Promise + func getPrivateKeys() -> Promise } enum EmailKeyManagerApiError: Error { case noGoogleIdToken case noPrivateKeysUrlString } + +enum EmailKeyManagerApiResult { + case success(decryptedResponse: DecryptedPrivateKeysResponse) + case noKeys + case keysAreNotDecrypted +} + extension EmailKeyManagerApiError: LocalizedError { var errorDescription: String? { switch self { @@ -30,9 +37,14 @@ extension EmailKeyManagerApiError: LocalizedError { class EmailKeyManagerApi: EmailKeyManagerApiType { private let organisationalRulesService: OrganisationalRulesServiceType + private let core: Core - init(organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService()) { + init( + organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService(), + core: Core = .shared + ) { self.organisationalRulesService = organisationalRulesService + self.core = core } func getPrivateKeysUrlString() -> String? { @@ -42,8 +54,8 @@ class EmailKeyManagerApi: EmailKeyManagerApiType { return "\(keyManagerUrlString)v1/keys/private" } - func getPrivateKeys() -> Promise { - Promise { [weak self] resolve, reject in + func getPrivateKeys() -> Promise { + Promise { [weak self] resolve, reject in guard let self = self else { throw AppErr.nilSelf } guard let urlString = self.getPrivateKeysUrlString() else { reject(EmailKeyManagerApiError.noPrivateKeysUrlString) @@ -68,7 +80,23 @@ class EmailKeyManagerApi: EmailKeyManagerApiType { ) let response = try awaitPromise(URLSession.shared.call(request)) let decryptedPrivateKeysResponse = try JSONDecoder().decode(DecryptedPrivateKeysResponse.self, from: response.data) - resolve(decryptedPrivateKeysResponse) + + if decryptedPrivateKeysResponse.privateKeys.isEmpty { + resolve(.success(decryptedResponse: .empty)) + } + + let privateKeys = decryptedPrivateKeysResponse.privateKeys + .map { $0.decryptedPrivateKey.data() } + let parsedPrivateKeys = privateKeys + .map { try? self.core.parseKeys(armoredOrBinary: $0) } + let areKeysDecrypted = parsedPrivateKeys + .compactMap { $0?.keyDetails.map { $0.isFullyDecrypted } } + .flatMap { $0 } + + if areKeysDecrypted.contains(false) { + resolve(.keysAreNotDecrypted) + } + resolve(.success(decryptedResponse: decryptedPrivateKeysResponse)) } } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index ac45a9037..33a49b2ff 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -2,7 +2,7 @@ "loading_title" = "Loading"; "sending_title" = "Sending"; "unknown_title" = "(unknown)"; -"retry_title" = "retry"; +"retry_title" = "Retry"; "ok" = "Ok"; "cancel" = "Cancel"; "open" = "Open"; @@ -210,6 +210,9 @@ "organisational_rules_must_submit_attester_error" = "Combination of rules (PRV_AUTOIMPORT_OR_AUTOGEN + ENFORCE_ATTESTER_SUBMIT) is not supported on this platform"; "organisational_rules_can_create_keys_error" = "Combination of rules (PRV_AUTOIMPORT_OR_AUTOGEN + missing NO_PRV_CREATE) is not supported on this platform"; "organisational_rules_ekm_private_keys_message" = "Ignoring %d keys returned by EKM %@ (not implemented)"; +"organisational_rules_ekm_empty_private_keys_error" = "There are no private keys configured for you. Please ask yout systems administrator or help desk"; +"organisational_rules_ekm_keys_are_not_decrypted_error" = "Received private keys are not fully decrypted. Please try login flow again"; // Email key manager api error "emai_keymanager_api_no_google_id_token_error_description" = "There is no Google ID token were found while getting client configuration"; + From f473dc17b8fc57fd7f6dce50641fb15cab93484e Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Mon, 9 Aug 2021 17:42:25 +0300 Subject: [PATCH 2/5] Added passphrase setup if any key present in EKM; --- FlowCrypt.xcodeproj/project.pbxproj | 8 +-- ...SetupCreatePassphraseViewController.swift} | 67 +++++++++++++++++-- .../Setup/SetupInitialViewController.swift | 18 +++-- FlowCrypt/Core/Core.swift | 5 ++ FlowCrypt/Core/CoreTypes.swift | 4 ++ .../Services/EmailKeyManagerApi.swift | 11 +-- 6 files changed, 87 insertions(+), 26 deletions(-) rename FlowCrypt/Controllers/Setup/{SetupGenerateKeyViewController.swift => SetupCreatePassphraseViewController.swift} (81%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 407d776ef..f7858587f 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -147,7 +147,7 @@ 9F9AAFFD2383E216000A00F1 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9AAFFC2383E216000A00F1 /* Document.swift */; }; 9F9ABC8723AC1EAA00D560E3 /* MessageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; - 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */; }; + 9FA405C7265AEBA50084D133 /* SetupCreatePassphraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupCreatePassphraseViewController.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */; }; @@ -559,7 +559,7 @@ 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = ""; }; 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; - 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupGenerateKeyViewController.swift; sourceTree = ""; }; + 9FA405C6265AEBA40084D133 /* SetupCreatePassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupCreatePassphraseViewController.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceError.swift; sourceTree = ""; }; @@ -1583,7 +1583,7 @@ isa = PBXGroup; children = ( 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */, - 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */, + 9FA405C6265AEBA40084D133 /* SetupCreatePassphraseViewController.swift */, C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */, 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */, 9FD364852381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift */, @@ -2625,7 +2625,7 @@ 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewContainerController.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, - 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */, + 9FA405C7265AEBA50084D133 /* SetupCreatePassphraseViewController.swift in Sources */, 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */, 9FEED1D2230DAD1E00700F8E /* InboxViewModel.swift in Sources */, 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseViewController.swift similarity index 81% rename from FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift rename to FlowCrypt/Controllers/Setup/SetupCreatePassphraseViewController.swift index c64fade53..dbd5167ac 100644 --- a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseViewController.swift @@ -22,6 +22,11 @@ enum CreateKeyError: Error { case conformingPassPhraseError } +enum CreatePassphraseWithExistingKeyError: Error { + // No private key was found + case noPrivateKey +} + /** * Controller which is responsible for generating a new key during setup * - User is sent here from **SetupInitialViewController** in case there are no backups found @@ -29,7 +34,7 @@ enum CreateKeyError: Error { * - After key is generated, user will be redirected to **main flow** (inbox view) */ -final class SetupGenerateKeyViewController: TableNodeViewController, PassPhraseSaveable { +final class SetupCreatePassphraseViewController: TableNodeViewController, PassPhraseSaveable { enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, action, subtitle } @@ -43,6 +48,7 @@ final class SetupGenerateKeyViewController: TableNodeViewController, PassPhraseS private let storage: DataServiceType private let keyStorage: KeyStorageType private let attester: AttesterApiType + private let keys: [CoreRes.ParseKeys] let passPhraseService: PassPhraseServiceType var shouldSaveLocally = true { @@ -62,6 +68,7 @@ final class SetupGenerateKeyViewController: TableNodeViewController, PassPhraseS init( user: UserId, + keys: [CoreRes.ParseKeys] = [], backupService: BackupServiceType = BackupService(), core: Core = .shared, router: GlobalRouterType = GlobalRouter(), @@ -80,7 +87,7 @@ final class SetupGenerateKeyViewController: TableNodeViewController, PassPhraseS self.attester = attester self.keyStorage = keyStorage self.passPhraseService = passPhraseService - + self.keys = keys super.init(node: TableNode()) } @@ -97,7 +104,7 @@ final class SetupGenerateKeyViewController: TableNodeViewController, PassPhraseS // MARK: - UI -extension SetupGenerateKeyViewController { +extension SetupCreatePassphraseViewController { private func setupUI() { node.delegate = self node.dataSource = self @@ -136,7 +143,7 @@ extension SetupGenerateKeyViewController { // MARK: - Setup -extension SetupGenerateKeyViewController { +extension SetupCreatePassphraseViewController { private func setupAccountWithGeneratedKey(with passPhrase: String) { Promise { [weak self] in guard let self = self else { return } @@ -187,6 +194,48 @@ extension SetupGenerateKeyViewController { } } + private func setupAccountWithExistingKeys(with passPhrase: String) { + Promise { [weak self] in + guard let self = self else { return } + self.showSpinner() + + try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) + + try self.keys.forEach { key in + try key.keyDetails.forEach { keyDetail in + guard let privateKey = keyDetail.private else { + throw CreatePassphraseWithExistingKeyError.noPrivateKey + } + let encryptedPrv = try self.core.encryptKey( + armoredPrv: privateKey, + passphrase: passPhrase + ) + let parsedKey = try self.core.parseKeys(armoredOrBinary: encryptedPrv.encryptedKey.data()) + parsedKey.keyDetails.forEach { keyDetails in + let passPhrase = PassPhrase(value: passPhrase, fingerprints: keyDetails.fingerprints) + self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) + } + try awaitPromise(self.backupService.backupToInbox(keys: parsedKey.keyDetails, for: self.user)) + self.keyStorage.addKeys(keyDetails: parsedKey.keyDetails, source: .generated, for: self.user.email) + } + } + } + .then(on: .main) { [weak self] in + self?.hideSpinner() + self?.moveToMainFlow() + } + .catch(on: .main) { [weak self] error in + guard let self = self else { return } + self.hideSpinner() + + let isErrorHandled = self.handleCommon(error: error) + + if !isErrorHandled { + self.showAlert(error: error, message: "Could not finish setup, please try again") + } + } + } + private func getUserId() throws -> UserId { guard let email = DataService.shared.email, !email.isEmpty else { throw CreateKeyError.missedUserEmail @@ -246,7 +295,7 @@ extension SetupGenerateKeyViewController { } } -extension SetupGenerateKeyViewController { +extension SetupCreatePassphraseViewController { private func moveToMainFlow() { router.proceed() } @@ -262,13 +311,17 @@ extension SetupGenerateKeyViewController { return } logger.logInfo("Setup account with \(passPhrase)") - setupAccountWithGeneratedKey(with: passPhrase) + if keys.isNotEmpty { + self.setupAccountWithExistingKeys(with: passPhrase) + } else { + setupAccountWithGeneratedKey(with: passPhrase) + } } } // MARK: - ASTableDelegate, ASTableDataSource -extension SetupGenerateKeyViewController: ASTableDelegate, ASTableDataSource { +extension SetupCreatePassphraseViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { parts.count } diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 53603b2b7..eb2d4bfec 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -157,16 +157,10 @@ extension SetupInitialViewController { private func fetchKeysFromEKM() { emailKeyManagerApi.getPrivateKeys() - .then { [weak self] result in - guard let urlString = self?.emailKeyManagerApi.getPrivateKeysUrlString() else { - fatalError("Private keys URL can not be nil at this point") - } - + .then(on: .main) { [weak self] result in switch result { - case .success(decryptedResponse: let response): - self?.showToast( - "organisational_rules_ekm_private_keys_message".localizeWithArguments(response.privateKeys.count, urlString) - ) + case .success(keys: let keys): + self?.proceedToSetupPassPhrase(keys: keys) case .noKeys: self?.showRetryAlert( message: "organisational_rules_ekm_empty_private_keys_error".localized, @@ -325,7 +319,11 @@ extension SetupInitialViewController { } private func proceedToCreatingNewKey() { - let viewController = SetupGenerateKeyViewController(user: user) + let viewController = SetupCreatePassphraseViewController(user: user) + navigationController?.pushViewController(viewController, animated: true) + } + private func proceedToSetupPassPhrase(keys: [CoreRes.ParseKeys]) { + let viewController = SetupCreatePassphraseViewController(user: user, keys: keys) navigationController?.pushViewController(viewController, animated: true) } diff --git a/FlowCrypt/Core/Core.swift b/FlowCrypt/Core/Core.swift index 6d1240c2f..ed1b64410 100644 --- a/FlowCrypt/Core/Core.swift +++ b/FlowCrypt/Core/Core.swift @@ -46,6 +46,11 @@ final class Core: KeyDecrypter, CoreComposeMessageType { return try r.json.decodeJson(as: CoreRes.DecryptKey.self) } + func encryptKey(armoredPrv: String, passphrase: String) throws -> CoreRes.EncryptKey { + let r = try call("encryptKey", jsonDict: ["armored": armoredPrv, "passphrase": passphrase], data: nil) + return try r.json.decodeJson(as: CoreRes.EncryptKey.self) + } + func parseDecryptMsg(encrypted: Data, keys: [PrvKeyInfo], msgPwd: String?, isEmail: Bool) throws -> CoreRes.ParseDecryptMsg { let json: [String : Any?]? = [ "keys": try keys.map { try $0.toJsonEncodedDict() }, diff --git a/FlowCrypt/Core/CoreTypes.swift b/FlowCrypt/Core/CoreTypes.swift index 0c8793bd4..ef42117cc 100644 --- a/FlowCrypt/Core/CoreTypes.swift +++ b/FlowCrypt/Core/CoreTypes.swift @@ -40,6 +40,10 @@ struct CoreRes { let decryptedKey: String? } + struct EncryptKey: Decodable { + let encryptedKey: String + } + struct GenerateKey: Decodable { let key: KeyDetails } diff --git a/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift b/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift index b8e0014c9..d29e3c9b5 100644 --- a/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift +++ b/FlowCrypt/Functionality/Services/EmailKeyManagerApi.swift @@ -20,7 +20,7 @@ enum EmailKeyManagerApiError: Error { } enum EmailKeyManagerApiResult { - case success(decryptedResponse: DecryptedPrivateKeysResponse) + case success(keys: [CoreRes.ParseKeys]) case noKeys case keysAreNotDecrypted } @@ -82,21 +82,22 @@ class EmailKeyManagerApi: EmailKeyManagerApiType { let decryptedPrivateKeysResponse = try JSONDecoder().decode(DecryptedPrivateKeysResponse.self, from: response.data) if decryptedPrivateKeysResponse.privateKeys.isEmpty { - resolve(.success(decryptedResponse: .empty)) + resolve(.noKeys) } let privateKeys = decryptedPrivateKeysResponse.privateKeys .map { $0.decryptedPrivateKey.data() } let parsedPrivateKeys = privateKeys - .map { try? self.core.parseKeys(armoredOrBinary: $0) } + .compactMap { try? self.core.parseKeys(armoredOrBinary: $0) } let areKeysDecrypted = parsedPrivateKeys - .compactMap { $0?.keyDetails.map { $0.isFullyDecrypted } } + .compactMap { $0.keyDetails.map { $0.isFullyDecrypted } } .flatMap { $0 } if areKeysDecrypted.contains(false) { resolve(.keysAreNotDecrypted) } - resolve(.success(decryptedResponse: decryptedPrivateKeysResponse)) + + resolve(.success(keys: parsedPrivateKeys)) } } } From 059a5fed34b9bbc031fa5409198c3e698ddc9580 Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Fri, 13 Aug 2021 19:07:54 +0300 Subject: [PATCH 3/5] Created abstract view controller for passphrase creation; Fixed PR comments; --- FlowCrypt.xcodeproj/project.pbxproj | 16 +- ...atePassphraseAbstractViewController.swift} | 185 +++--------------- .../Setup/SetupEKMKeyViewController.swift | 123 ++++++++++++ .../SetupGenerateKeyViewController.swift | 141 +++++++++++++ .../Setup/SetupInitialViewController.swift | 11 +- FlowCrypt/Models/Realm Models/KeyInfo.swift | 1 + 6 files changed, 311 insertions(+), 166 deletions(-) rename FlowCrypt/Controllers/Setup/{SetupCreatePassphraseViewController.swift => SetupCreatePassphraseAbstractViewController.swift} (54%) create mode 100644 FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift create mode 100644 FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 82bc80884..0471ec099 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 214A023A26A3029700C24066 /* EmailKeyManagerApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214A023926A3029700C24066 /* EmailKeyManagerApi.swift */; }; 215002A32690B1DD00980DDD /* client_configuraion_with_unknown_flag.json in Resources */ = {isa = PBXBuildFile; fileRef = 215002A22690B1DD00980DDD /* client_configuraion_with_unknown_flag.json */; }; 215897E8267A553300423694 /* FilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215897E7267A553200423694 /* FilesManager.swift */; }; + 21750D7D26C6AFA6007E6A6F /* SetupEKMKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21750D7C26C6AFA6007E6A6F /* SetupEKMKeyViewController.swift */; }; + 21750D7F26C6C1E3007E6A6F /* SetupCreatePassphraseAbstractViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21750D7E26C6C1E3007E6A6F /* SetupCreatePassphraseAbstractViewController.swift */; }; 2196A2202684B9BE001B9E00 /* URLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2196A21F2684B9BE001B9E00 /* URLExtension.swift */; }; 21B15C9426AA00A400D8522B /* ClientConfigurationServiceResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B15C9326AA00A400D8522B /* ClientConfigurationServiceResults.swift */; }; 21C7DEFC26669A3700C44800 /* CalendarExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7DEFB26669A3700C44800 /* CalendarExtension.swift */; }; @@ -149,7 +151,7 @@ 9F9AAFFD2383E216000A00F1 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9AAFFC2383E216000A00F1 /* Document.swift */; }; 9F9ABC8723AC1EAA00D560E3 /* MessageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */; }; 9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; }; - 9FA405C7265AEBA50084D133 /* SetupCreatePassphraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupCreatePassphraseViewController.swift */; }; + 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */; }; 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; }; 9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; }; 9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */; }; @@ -391,6 +393,8 @@ 214A023926A3029700C24066 /* EmailKeyManagerApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailKeyManagerApi.swift; sourceTree = ""; }; 215002A22690B1DD00980DDD /* client_configuraion_with_unknown_flag.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = client_configuraion_with_unknown_flag.json; sourceTree = ""; }; 215897E7267A553200423694 /* FilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesManager.swift; sourceTree = ""; }; + 21750D7C26C6AFA6007E6A6F /* SetupEKMKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupEKMKeyViewController.swift; sourceTree = ""; }; + 21750D7E26C6C1E3007E6A6F /* SetupCreatePassphraseAbstractViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupCreatePassphraseAbstractViewController.swift; sourceTree = ""; }; 2196A21F2684B9BE001B9E00 /* URLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtension.swift; sourceTree = ""; }; 21B15C9326AA00A400D8522B /* ClientConfigurationServiceResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConfigurationServiceResults.swift; sourceTree = ""; }; 21C7DEFB26669A3700C44800 /* CalendarExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarExtension.swift; sourceTree = ""; }; @@ -562,7 +566,7 @@ 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = ""; }; 9FA0157926565B7800CBBA05 /* KeyMethodsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyMethodsTest.swift; sourceTree = ""; }; 9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; - 9FA405C6265AEBA40084D133 /* SetupCreatePassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupCreatePassphraseViewController.swift; sourceTree = ""; }; + 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupGenerateKeyViewController.swift; sourceTree = ""; }; 9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = ""; }; 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = ""; }; 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceError.swift; sourceTree = ""; }; @@ -1583,7 +1587,9 @@ isa = PBXGroup; children = ( 9F4163B7265ED61C00106194 /* SetupInitialViewController.swift */, - 9FA405C6265AEBA40084D133 /* SetupCreatePassphraseViewController.swift */, + 21750D7E26C6C1E3007E6A6F /* SetupCreatePassphraseAbstractViewController.swift */, + 9FA405C6265AEBA40084D133 /* SetupGenerateKeyViewController.swift */, + 21750D7C26C6AFA6007E6A6F /* SetupEKMKeyViewController.swift */, C192421E1EC48B6900C3D251 /* SetupBackupsViewController.swift */, 9F268890237DC55600428A94 /* SetupManuallyImportKeyViewController.swift */, 9FD364852381EFCB00657302 /* SetupManuallyEnterPassPhraseViewController.swift */, @@ -2559,6 +2565,7 @@ 5A39F437239ECC23001F4607 /* KeySettingsViewController.swift in Sources */, 9FF0673325520DE400FCC9E6 /* GmailService+send.swift in Sources */, D20D3C892520B67C00D4AA9A /* BackupOptionsViewController.swift in Sources */, + 21750D7D26C6AFA6007E6A6F /* SetupEKMKeyViewController.swift in Sources */, 211392A5266511E6009202EC /* PubLookup.swift in Sources */, D20D3C672520AB1000D4AA9A /* BackupViewController.swift in Sources */, D2E26F6324F1698100612AF1 /* ContactsListViewController.swift in Sources */, @@ -2611,6 +2618,7 @@ 9F0C3C102316DD5B00299985 /* GoogleUserService.swift in Sources */, D24F4C2223E2359B00C5EEE4 /* BootstrapViewController.swift in Sources */, D211CE7623FC36BC00D1CE38 /* UIColorExtension.swift in Sources */, + 21750D7F26C6C1E3007E6A6F /* SetupCreatePassphraseAbstractViewController.swift in Sources */, 9FC7EBAA266EBD3700F3BF5D /* InMemoryPassPhraseStorage.swift in Sources */, 9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */, D2FF6966243115EC007182F0 /* SetupImapViewController.swift in Sources */, @@ -2626,7 +2634,7 @@ 9F79229426696B9300DA3D80 /* KeyDataStorage.swift in Sources */, 9F82D352256D74FA0069A702 /* InboxViewContainerController.swift in Sources */, D227C0E3250538100070F805 /* LocalFoldersProvider.swift in Sources */, - 9FA405C7265AEBA50084D133 /* SetupCreatePassphraseViewController.swift in Sources */, + 9FA405C7265AEBA50084D133 /* SetupGenerateKeyViewController.swift in Sources */, 9F6EE1552597399D0059BA51 /* BackupProvider.swift in Sources */, 9FEED1D2230DAD1E00700F8E /* InboxViewModel.swift in Sources */, 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, diff --git a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift similarity index 54% rename from FlowCrypt/Controllers/Setup/SetupCreatePassphraseViewController.swift rename to FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift index 528c1d7f3..3932db6d1 100644 --- a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift @@ -1,8 +1,8 @@ // -// CreatePrivateKeyViewController.swift +// SetupCreatePassphraseAbstractViewController.swift // FlowCrypt // -// Created by Anton Kharchevskyi on 23.05.2021. +// Created by Yevhen Kyivskyi on 13.08.2021. // Copyright © 2021 FlowCrypt Limited. All rights reserved. // @@ -11,45 +11,26 @@ import FlowCryptCommon import FlowCryptUI import Promises -enum CreateKeyError: Error { - case weakPassPhrase(_ strength: CoreRes.ZxcvbnStrengthBar) - // Missing user email - case missedUserEmail - // Missing user name - case missedUserName - // Pass phrases don't match - case doesntMatch - // silent abort - case conformingPassPhraseError -} - -enum CreatePassphraseWithExistingKeyError: Error { - // No private key was found - case noPrivateKey -} - /** - * Controller which is responsible for generating a new key during setup - * - User is sent here from **SetupInitialViewController** in case there are no backups found - * - Here user can enter a pass phrase (can be saved in memory or in encrypted storage) and generate a key - * - After key is generated, user will be redirected to **main flow** (inbox view) + * Controller which decalres a base logic for passphrase setup + * - Has not to have an instance! */ -final class SetupCreatePassphraseViewController: TableNodeViewController, PassPhraseSaveable { +class SetupCreatePassphraseAbstractViewController: TableNodeViewController, PassPhraseSaveable { enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, action, subtitle } - private let parts = Parts.allCases - private let decorator: SetupViewDecorator - private let core: Core - private let router: GlobalRouterType - private let user: UserId - private let backupService: BackupServiceType - private let storage: DataServiceType - private let keyStorage: KeyStorageType - private let attester: AttesterApiType - private let keys: [CoreRes.ParseKeys] + var parts: [Parts] { + Parts.allCases + } + + let decorator: SetupViewDecorator + let core: Core + let router: GlobalRouterType + let user: UserId + let storage: DataServiceType + let keyStorage: KeyStorageType let passPhraseService: PassPhraseServiceType var shouldSaveLocally = true { @@ -69,26 +50,20 @@ final class SetupCreatePassphraseViewController: TableNodeViewController, PassPh init( user: UserId, - keys: [CoreRes.ParseKeys] = [], - backupService: BackupServiceType = BackupService(), core: Core = .shared, router: GlobalRouterType = GlobalRouter(), decorator: SetupViewDecorator = SetupViewDecorator(), storage: DataServiceType = DataService.shared, keyStorage: KeyStorageType = KeyDataStorage(), - attester: AttesterApiType = AttesterApi(), passPhraseService: PassPhraseServiceType = PassPhraseService() ) { self.user = user self.core = core self.router = router self.decorator = decorator - self.backupService = backupService self.storage = storage - self.attester = attester self.keyStorage = keyStorage self.passPhraseService = passPhraseService - self.keys = keys super.init(node: TableNode()) } @@ -101,12 +76,16 @@ final class SetupCreatePassphraseViewController: TableNodeViewController, PassPh super.viewDidLoad() setupUI() } + + func setupAccount(with passphrase: String) { + fatalError("This method has to be overriden") + } } // MARK: - UI -extension SetupCreatePassphraseViewController { - private func setupUI() { +extension SetupCreatePassphraseAbstractViewController { + func setupUI() { node.delegate = self node.dataSource = self @@ -144,110 +123,9 @@ extension SetupCreatePassphraseViewController { // MARK: - Setup -extension SetupCreatePassphraseViewController { - private func setupAccountWithGeneratedKey(with passPhrase: String) { - Promise { [weak self] in - guard let self = self else { return } - self.showSpinner() - - let userId = try self.getUserId() - - try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) - - let encryptedPrv = try self.core.generateKey(passphrase: passPhrase, variant: .curve25519, userIds: [userId]) - - try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) - - let passPhrase = PassPhrase(value: passPhrase, fingerprints: encryptedPrv.key.fingerprints) - - self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated, for: self.user.email) - self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) - - let updateKey = self.attester.updateKey( - email: userId.email, - pubkey: encryptedPrv.key.public, - token: self.storage.token - ) - - try awaitPromise(self.alertAndSkipOnRejection( - updateKey, - fail: "Failed to submit Public Key") - ) - let testWelcome = self.attester.testWelcome(email: userId.email, pubkey: encryptedPrv.key.public) - try awaitPromise(self.alertAndSkipOnRejection( - testWelcome, - fail: "Failed to send you welcome email") - ) - } - .then(on: .main) { [weak self] in - self?.hideSpinner() - self?.moveToMainFlow() - } - .catch(on: .main) { [weak self] error in - guard let self = self else { return } - self.hideSpinner() - - let isErrorHandled = self.handleCommon(error: error) - - if !isErrorHandled { - self.showAlert(error: error, message: "Could not finish setup, please try again") - } - } - } - - private func setupAccountWithExistingKeys(with passPhrase: String) { - Promise { [weak self] in - guard let self = self else { return } - self.showSpinner() - - try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) - - try self.keys.forEach { key in - try key.keyDetails.forEach { keyDetail in - guard let privateKey = keyDetail.private else { - throw CreatePassphraseWithExistingKeyError.noPrivateKey - } - let encryptedPrv = try self.core.encryptKey( - armoredPrv: privateKey, - passphrase: passPhrase - ) - let parsedKey = try self.core.parseKeys(armoredOrBinary: encryptedPrv.encryptedKey.data()) - parsedKey.keyDetails.forEach { keyDetails in - let passPhrase = PassPhrase(value: passPhrase, fingerprints: keyDetails.fingerprints) - self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) - } - try awaitPromise(self.backupService.backupToInbox(keys: parsedKey.keyDetails, for: self.user)) - self.keyStorage.addKeys(keyDetails: parsedKey.keyDetails, source: .generated, for: self.user.email) - } - } - } - .then(on: .main) { [weak self] in - self?.hideSpinner() - self?.moveToMainFlow() - } - .catch(on: .main) { [weak self] error in - guard let self = self else { return } - self.hideSpinner() - - let isErrorHandled = self.handleCommon(error: error) - - if !isErrorHandled { - self.showAlert(error: error, message: "Could not finish setup, please try again") - } - } - } - - private func getUserId() throws -> UserId { - guard let email = DataService.shared.email, !email.isEmpty else { - throw CreateKeyError.missedUserEmail - } - guard let name = DataService.shared.email, !name.isEmpty else { - throw CreateKeyError.missedUserName - } - return UserId(email: email, name: name) - } +extension SetupCreatePassphraseAbstractViewController { - private func validateAndConfirmNewPassPhraseOrReject(passPhrase: String) -> Promise { + func validateAndConfirmNewPassPhraseOrReject(passPhrase: String) -> Promise { Promise { [weak self] in guard let self = self else { throw AppErr.nilSelf } @@ -296,8 +174,8 @@ extension SetupCreatePassphraseViewController { } } -extension SetupCreatePassphraseViewController { - private func moveToMainFlow() { +extension SetupCreatePassphraseAbstractViewController { + func moveToMainFlow() { router.proceed() } @@ -312,24 +190,21 @@ extension SetupCreatePassphraseViewController { return } logger.logInfo("Setup account with \(passPhrase)") - if keys.isNotEmpty { - self.setupAccountWithExistingKeys(with: passPhrase) - } else { - setupAccountWithGeneratedKey(with: passPhrase) - } + setupAccount(with: passPhrase) } } // MARK: - ASTableDelegate, ASTableDataSource -extension SetupCreatePassphraseViewController: ASTableDelegate, ASTableDataSource { +extension SetupCreatePassphraseAbstractViewController: ASTableDelegate, ASTableDataSource { func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int { parts.count } func tableNode(_: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { return { [weak self] in - guard let self = self, let part = Parts(rawValue: indexPath.row) else { return ASCellNode() } + guard let self = self else { return ASCellNode() } + let part = self.parts[indexPath.row] switch part { case .title: return SetupTitleNode( @@ -387,7 +262,7 @@ extension SetupCreatePassphraseViewController: ASTableDelegate, ASTableDataSourc } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { - guard let part = Parts(rawValue: indexPath.row) else { return } + let part = parts[indexPath.row] switch part { case .description: diff --git a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift new file mode 100644 index 000000000..15651d54e --- /dev/null +++ b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift @@ -0,0 +1,123 @@ +// +// SetupEKMKeyViewController.swift +// FlowCrypt +// +// Created by Yevhen Kyivskyi on 13.08.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptCommon +import FlowCryptUI +import Promises + +enum CreatePassphraseWithExistingKeyError: Error { + // No private key was found + case noPrivateKey +} + +/** + * Controller which is responsible for setting up a keys received from EKM + * - User is sent here from **SetupInitialViewController** in case he has keys on EKM + * - Here user can enter a pass phrase (will be saved in memory) + * - After passphrase is set up, user will be redirected to **main flow** (inbox view) + */ + +final class SetupEKMKeyViewController: SetupCreatePassphraseAbstractViewController { + + override var parts: [SetupCreatePassphraseAbstractViewController.Parts] { + SetupCreatePassphraseAbstractViewController.Parts.ekmKeysSetup + } + private let keys: [CoreRes.ParseKeys] + + private lazy var logger = Logger.nested(in: Self.self, with: .setup) + + init( + user: UserId, + keys: [CoreRes.ParseKeys] = [], + core: Core = .shared, + router: GlobalRouterType = GlobalRouter(), + decorator: SetupViewDecorator = SetupViewDecorator(), + storage: DataServiceType = DataService.shared, + keyStorage: KeyStorageType = KeyDataStorage(), + passPhraseService: PassPhraseServiceType = PassPhraseService() + ) { + self.keys = keys + super.init( + user: user, + core: core, + router: router, + decorator: decorator, + storage: storage, + keyStorage: keyStorage, + passPhraseService: passPhraseService + ) + self.shouldSaveLocally = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + override func setupAccount(with passphrase: String) { + setupAccountWithKeysFetchedFromEkm(with: passphrase) + } +} + +// MARK: - Setup + +extension SetupEKMKeyViewController { + + private func setupAccountWithKeysFetchedFromEkm(with passPhrase: String) { + Promise { [weak self] in + guard let self = self else { return } + self.showSpinner() + + try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) + + try self.keys.forEach { key in + try key.keyDetails.forEach { keyDetail in + guard let privateKey = keyDetail.private else { + throw CreatePassphraseWithExistingKeyError.noPrivateKey + } + let encryptedPrv = try self.core.encryptKey( + armoredPrv: privateKey, + passphrase: passPhrase + ) + let parsedKey = try self.core.parseKeys(armoredOrBinary: encryptedPrv.encryptedKey.data()) + parsedKey.keyDetails.forEach { keyDetails in + let passPhrase = PassPhrase(value: passPhrase, fingerprints: keyDetails.fingerprints) + self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) + } + self.keyStorage.addKeys(keyDetails: parsedKey.keyDetails, source: .ekm, for: self.user.email) + } + } + } + .then(on: .main) { [weak self] in + self?.hideSpinner() + self?.moveToMainFlow() + } + .catch(on: .main) { [weak self] error in + guard let self = self else { return } + self.hideSpinner() + + let isErrorHandled = self.handleCommon(error: error) + + if !isErrorHandled { + self.showAlert(error: error, message: "Could not finish setup, please try again") + } + } + } +} + +extension SetupCreatePassphraseAbstractViewController.Parts { + static var ekmKeysSetup: [SetupCreatePassphraseAbstractViewController.Parts] { + return [.title, .description, .passPhrase, .divider, .action, .subtitle] + } +} diff --git a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift new file mode 100644 index 000000000..c5a4508d9 --- /dev/null +++ b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift @@ -0,0 +1,141 @@ +// +// CreatePrivateKeyViewController.swift +// FlowCrypt +// +// Created by Anton Kharchevskyi on 23.05.2021. +// Copyright © 2021 FlowCrypt Limited. All rights reserved. +// + +import AsyncDisplayKit +import FlowCryptCommon +import FlowCryptUI +import Promises + +enum CreateKeyError: Error { + case weakPassPhrase(_ strength: CoreRes.ZxcvbnStrengthBar) + // Missing user email + case missedUserEmail + // Missing user name + case missedUserName + // Pass phrases don't match + case doesntMatch + // silent abort + case conformingPassPhraseError +} + +/** + * Controller which is responsible for generating a new key during setup + * - User is sent here from **SetupInitialViewController** in case there are no backups found + * - Here user can enter a pass phrase (can be saved in memory or in encrypted storage) and generate a key + * - After key is generated, user will be redirected to **main flow** (inbox view) + */ + +final class SetupGenerateKeyViewController: SetupCreatePassphraseAbstractViewController { + + private let backupService: BackupServiceType + private let attester: AttesterApiType + + private lazy var logger = Logger.nested(in: Self.self, with: .setup) + + init( + user: UserId, + backupService: BackupServiceType = BackupService(), + core: Core = .shared, + router: GlobalRouterType = GlobalRouter(), + decorator: SetupViewDecorator = SetupViewDecorator(), + storage: DataServiceType = DataService.shared, + keyStorage: KeyStorageType = KeyDataStorage(), + attester: AttesterApiType = AttesterApi(), + passPhraseService: PassPhraseServiceType = PassPhraseService() + ) { + self.backupService = backupService + self.attester = attester + super.init( + user: user, + core: core, + router: router, + decorator: decorator, + storage: storage, + keyStorage: keyStorage, + passPhraseService: passPhraseService + ) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + override func setupAccount(with passphrase: String) { + setupAccountWithGeneratedKey(with: passphrase) + } +} + +// MARK: - Setup + +extension SetupGenerateKeyViewController { + private func setupAccountWithGeneratedKey(with passPhrase: String) { + Promise { [weak self] in + guard let self = self else { return } + self.showSpinner() + + let userId = try self.getUserId() + + try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) + + let encryptedPrv = try self.core.generateKey(passphrase: passPhrase, variant: .curve25519, userIds: [userId]) + + try awaitPromise(self.backupService.backupToInbox(keys: [encryptedPrv.key], for: self.user)) + + let passPhrase = PassPhrase(value: passPhrase, fingerprints: encryptedPrv.key.fingerprints) + + self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated, for: self.user.email) + self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) + + let updateKey = self.attester.updateKey( + email: userId.email, + pubkey: encryptedPrv.key.public, + token: self.storage.token + ) + + try awaitPromise(self.alertAndSkipOnRejection( + updateKey, + fail: "Failed to submit Public Key") + ) + let testWelcome = self.attester.testWelcome(email: userId.email, pubkey: encryptedPrv.key.public) + try awaitPromise(self.alertAndSkipOnRejection( + testWelcome, + fail: "Failed to send you welcome email") + ) + } + .then(on: .main) { [weak self] in + self?.hideSpinner() + self?.moveToMainFlow() + } + .catch(on: .main) { [weak self] error in + guard let self = self else { return } + self.hideSpinner() + + let isErrorHandled = self.handleCommon(error: error) + + if !isErrorHandled { + self.showAlert(error: error, message: "Could not finish setup, please try again") + } + } + } + + private func getUserId() throws -> UserId { + guard let email = DataService.shared.email, !email.isEmpty else { + throw CreateKeyError.missedUserEmail + } + guard let name = DataService.shared.email, !name.isEmpty else { + throw CreateKeyError.missedUserName + } + return UserId(email: email, name: name) + } +} diff --git a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift index 83edd48eb..beb337c44 100644 --- a/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupInitialViewController.swift @@ -161,7 +161,7 @@ extension SetupInitialViewController { .then(on: .main) { [weak self] result in switch result { case .success(keys: let keys): - self?.proceedToSetupPassPhrase(keys: keys) + self?.proceedToSetupWithEKMKeys(keys: keys) case .noKeys: self?.showRetryAlert( message: "organisational_rules_ekm_empty_private_keys_error".localized, @@ -177,9 +177,6 @@ extension SetupInitialViewController { self?.router.signOut() }) } - // todo - this is temporary, until we finish EKM integration - // instead we should use the keys from EKM for setup - self?.state = .searchingKeyBackupsInInbox } .catch { [weak self] error in if case .noPrivateKeysUrlString = error as? EmailKeyManagerApiError { @@ -320,11 +317,11 @@ extension SetupInitialViewController { } private func proceedToCreatingNewKey() { - let viewController = SetupCreatePassphraseViewController(user: user) + let viewController = SetupGenerateKeyViewController(user: user) navigationController?.pushViewController(viewController, animated: true) } - private func proceedToSetupPassPhrase(keys: [CoreRes.ParseKeys]) { - let viewController = SetupCreatePassphraseViewController(user: user, keys: keys) + private func proceedToSetupWithEKMKeys(keys: [CoreRes.ParseKeys]) { + let viewController = SetupEKMKeyViewController(user: user, keys: keys) navigationController?.pushViewController(viewController, animated: true) } diff --git a/FlowCrypt/Models/Realm Models/KeyInfo.swift b/FlowCrypt/Models/Realm Models/KeyInfo.swift index 44e013fdf..a0d65c50c 100644 --- a/FlowCrypt/Models/Realm Models/KeyInfo.swift +++ b/FlowCrypt/Models/Realm Models/KeyInfo.swift @@ -8,6 +8,7 @@ enum KeySource: String { case backup case generated case imported + case ekm } enum KeyInfoError: Error { From afee2b778b6bcfa52d61477ba18f9fb8d12594d4 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 15 Aug 2021 06:33:25 +0800 Subject: [PATCH 4/5] fix bug when more than one key is used --- FlowCrypt.xcodeproj/project.pbxproj | 2 +- FlowCrypt/Controllers/Setup/PassPraseSaveable.swift | 6 +++--- .../Controllers/Setup/SetupBackupsViewController.swift | 8 ++++---- .../SetupCreatePassphraseAbstractViewController.swift | 6 +++--- .../Controllers/Setup/SetupEKMKeyViewController.swift | 10 +++++----- .../Setup/SetupGenerateKeyViewController.swift | 2 +- .../SetupManuallyEnterPassPhraseViewController.swift | 10 +++++----- .../Realm Models}/PassPhraseObject.swift | 0 8 files changed, 22 insertions(+), 22 deletions(-) rename FlowCrypt/{Core/Models => Models/Realm Models}/PassPhraseObject.swift (100%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index fb320bc6e..2c2d75243 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -1045,6 +1045,7 @@ 9F1B49E02624E19D00420472 /* Realm Models */ = { isa = PBXGroup; children = ( + 9FC7EAB2266A404D00F3BF5D /* PassPhraseObject.swift */, D2F41372243CC7990066AFB5 /* UserObject.swift */, D27B911C24EFE806002DF0A1 /* ContactObject.swift */, D2E26F6924F25AB800612AF1 /* KeyAlgoObject.swift */, @@ -1634,7 +1635,6 @@ D212D36324C1AC4800035991 /* KeyId.swift */, D212D35C24C1AACF00035991 /* PrvKeyInfo.swift */, D2E26F6B24F25B1F00612AF1 /* KeyAlgo.swift */, - 9FC7EAB2266A404D00F3BF5D /* PassPhraseObject.swift */, 2133D51726A1E45300CC686F /* DecryptedPrivateKey.swift */, ); path = Models; diff --git a/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift b/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift index 187be7eec..7a4eb67c0 100644 --- a/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift +++ b/FlowCrypt/Controllers/Setup/PassPraseSaveable.swift @@ -9,7 +9,7 @@ import FlowCryptUI protocol PassPhraseSaveable { - var shouldSaveLocally: Bool { get set } + var shouldStorePassPhrase: Bool { get set } var passPhraseIndexes: [IndexPath] { get } var saveLocallyNode: CellNode { get } var saveInMemoryNode: CellNode { get } @@ -26,11 +26,11 @@ extension PassPhraseSaveable where Self: TableNodeViewController { } var saveLocallyNode: CellNode { - CheckBoxTextNode(input: .passPhraseLocally(isSelected: self.shouldSaveLocally)) + CheckBoxTextNode(input: .passPhraseLocally(isSelected: self.shouldStorePassPhrase)) } var saveInMemoryNode: CellNode { - CheckBoxTextNode(input: .passPhraseMemory(isSelected: !self.shouldSaveLocally)) + CheckBoxTextNode(input: .passPhraseMemory(isSelected: !self.shouldStorePassPhrase)) } func showPassPhraseErrorAlert() { diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index 0365c2524..b12ead124 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -34,7 +34,7 @@ final class SetupBackupsViewController: TableNodeViewController, PassPhraseSavea private var passPhrase: String? - var shouldSaveLocally = true { + var shouldStorePassPhrase = true { didSet { handleSelectedPassPhraseOption() } @@ -150,7 +150,7 @@ extension SetupBackupsViewController { PassPhrase(value: passPhrase, fingerprints: $0.fingerprints) } .forEach { - passPhraseService.savePassPhrase(with: $0, inStorage: shouldSaveLocally) + passPhraseService.savePassPhrase(with: $0, inStorage: shouldStorePassPhrase) } // save keys @@ -256,9 +256,9 @@ extension SetupBackupsViewController: ASTableDelegate, ASTableDataSource { switch part { case .saveLocally: - shouldSaveLocally = true + shouldStorePassPhrase = true case .saveInMemory: - shouldSaveLocally = false + shouldStorePassPhrase = false default: break } diff --git a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift index 3932db6d1..cb49f7f70 100644 --- a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift @@ -33,7 +33,7 @@ class SetupCreatePassphraseAbstractViewController: TableNodeViewController, Pass let keyStorage: KeyStorageType let passPhraseService: PassPhraseServiceType - var shouldSaveLocally = true { + var shouldStorePassPhrase = true { didSet { handleSelectedPassPhraseOption() } @@ -268,9 +268,9 @@ extension SetupCreatePassphraseAbstractViewController: ASTableDelegate, ASTableD case .description: showChoosingOptions() case .saveLocally: - shouldSaveLocally = true + shouldStorePassPhrase = true case .saveInMemory: - shouldSaveLocally = false + shouldStorePassPhrase = false default: break } diff --git a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift index 15651d54e..9a274527a 100644 --- a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift @@ -52,7 +52,7 @@ final class SetupEKMKeyViewController: SetupCreatePassphraseAbstractViewControll keyStorage: keyStorage, passPhraseService: passPhraseService ) - self.shouldSaveLocally = true + self.shouldStorePassPhrase = true } @available(*, unavailable) @@ -81,6 +81,7 @@ extension SetupEKMKeyViewController { try awaitPromise(self.validateAndConfirmNewPassPhraseOrReject(passPhrase: passPhrase)) + var allFingerprints: [String] = [] try self.keys.forEach { key in try key.keyDetails.forEach { keyDetail in guard let privateKey = keyDetail.private else { @@ -91,13 +92,12 @@ extension SetupEKMKeyViewController { passphrase: passPhrase ) let parsedKey = try self.core.parseKeys(armoredOrBinary: encryptedPrv.encryptedKey.data()) - parsedKey.keyDetails.forEach { keyDetails in - let passPhrase = PassPhrase(value: passPhrase, fingerprints: keyDetails.fingerprints) - self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) - } self.keyStorage.addKeys(keyDetails: parsedKey.keyDetails, source: .ekm, for: self.user.email) + allFingerprints.append(contentsOf: parsedKey.keyDetails.flatMap { $0.fingerprints }) } } + let passPhrase = PassPhrase(value: passPhrase, fingerprints: allFingerprints.unique()) + self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldStorePassPhrase) } .then(on: .main) { [weak self] in self?.hideSpinner() diff --git a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift index c5a4508d9..50b5472f7 100644 --- a/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupGenerateKeyViewController.swift @@ -95,7 +95,7 @@ extension SetupGenerateKeyViewController { let passPhrase = PassPhrase(value: passPhrase, fingerprints: encryptedPrv.key.fingerprints) self.keyStorage.addKeys(keyDetails: [encryptedPrv.key], source: .generated, for: self.user.email) - self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldSaveLocally) + self.passPhraseService.savePassPhrase(with: passPhrase, inStorage: self.shouldStorePassPhrase) let updateKey = self.attester.updateKey( email: userId.email, diff --git a/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift b/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift index 51ad8ad12..a7cf6ae39 100644 --- a/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupManuallyEnterPassPhraseViewController.swift @@ -34,7 +34,7 @@ final class SetupManuallyEnterPassPhraseViewController: TableNodeViewController, private var passPhrase: String? - var shouldSaveLocally = true { + var shouldStorePassPhrase = true { didSet { handleSelectedPassPhraseOption() } @@ -197,9 +197,9 @@ extension SetupManuallyEnterPassPhraseViewController: ASTableDelegate, ASTableDa switch part { case .saveLocally: - shouldSaveLocally = true + shouldStorePassPhrase = true case .saveInMemory: - shouldSaveLocally = false + shouldStorePassPhrase = false default: break } @@ -249,7 +249,7 @@ extension SetupManuallyEnterPassPhraseViewController { PassPhrase(value: passPhrase, fingerprints: $0.fingerprints) } .forEach { - passPhraseService.updatePassPhrase(with: $0, inStorage: shouldSaveLocally) + passPhraseService.updatePassPhrase(with: $0, inStorage: shouldStorePassPhrase) } newKeysToAdd @@ -257,7 +257,7 @@ extension SetupManuallyEnterPassPhraseViewController { PassPhrase(value: passPhrase, fingerprints: $0.fingerprints) } .forEach { - passPhraseService.savePassPhrase(with: $0, inStorage: shouldSaveLocally) + passPhraseService.savePassPhrase(with: $0, inStorage: shouldStorePassPhrase) } hideSpinner() diff --git a/FlowCrypt/Core/Models/PassPhraseObject.swift b/FlowCrypt/Models/Realm Models/PassPhraseObject.swift similarity index 100% rename from FlowCrypt/Core/Models/PassPhraseObject.swift rename to FlowCrypt/Models/Realm Models/PassPhraseObject.swift From f410a5a91d02da85eaaf08cdf8f07c5175ce6975 Mon Sep 17 00:00:00 2001 From: ykyivskyi-gd Date: Sun, 15 Aug 2021 11:01:08 +0300 Subject: [PATCH 5/5] Saving passphrase in memory for EKM flow; --- FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift index 9a274527a..5dea07992 100644 --- a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift @@ -52,7 +52,7 @@ final class SetupEKMKeyViewController: SetupCreatePassphraseAbstractViewControll keyStorage: keyStorage, passPhraseService: passPhraseService ) - self.shouldStorePassPhrase = true + self.shouldStorePassPhrase = false } @available(*, unavailable)