diff --git a/pass/Controllers/AddPasswordTableViewController.swift b/pass/Controllers/AddPasswordTableViewController.swift index dea0558..a6ddf20 100644 --- a/pass/Controllers/AddPasswordTableViewController.swift +++ b/pass/Controllers/AddPasswordTableViewController.swift @@ -42,14 +42,6 @@ class AddPasswordTableViewController: PasswordEditorTableViewController { Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil) return false } - - // check "/" - guard nameCell.getContent()!.contains("/") == false else { - let alertTitle = "Cannot Add Password" - let alertMessage = "Illegal character." - Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil) - return false - } } return true } @@ -73,7 +65,9 @@ class AddPasswordTableViewController: PasswordEditorTableViewController { } else { plainText = "\(cellContents["password"]!)" } - password = Password(name: cellContents["name"]!, plainText: plainText) + let name = URL(string: cellContents["name"]!)!.lastPathComponent + let url = URL(string: cellContents["name"]!)!.appendingPathExtension("gpg") + password = Password(name: name, url: url, plainText: plainText) } } } diff --git a/pass/Controllers/EditPasswordTableViewController.swift b/pass/Controllers/EditPasswordTableViewController.swift index 0caf2f8..7d12f53 100644 --- a/pass/Controllers/EditPasswordTableViewController.swift +++ b/pass/Controllers/EditPasswordTableViewController.swift @@ -11,7 +11,7 @@ import UIKit class EditPasswordTableViewController: PasswordEditorTableViewController { override func viewDidLoad() { tableData = [ - [[.type: PasswordEditorCellType.textFieldCell, .title: "name", .content: password!.name]], + [[.type: PasswordEditorCellType.textFieldCell, .title: "name", .content: password!.namePath]], [[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password], [.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]], [[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]], @@ -23,15 +23,8 @@ class EditPasswordTableViewController: PasswordEditorTableViewController { override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { if identifier == "saveEditPasswordSegue" { - if let nameCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? ContentTableViewCell { - if nameCell.getContent() != password?.name { - let alertTitle = "Cannot Save Edit" - let alertMessage = "Editing name is not supported." - Utils.alert(title: alertTitle, message: alertMessage, controller: self) { - nameCell.setContent(content: self.password!.name) - } - return false - } + if let _ = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? ContentTableViewCell { + // TODO: do some checks here } } return true @@ -54,9 +47,13 @@ class EditPasswordTableViewController: PasswordEditorTableViewController { if cellContents["additions"]! != "" { plainText = "\(cellContents["password"]!)\n\(cellContents["additions"]!)" } else { - plainText = "\(cellContents["password"]!)" + plainText = "\(cellContents["password"]!)\n" + } + let name = URL(string: cellContents["name"]!)!.lastPathComponent + let url = URL(string: cellContents["name"]!)!.appendingPathExtension("gpg") + if password!.plainText != plainText || password!.url!.path != url.path { + password!.updatePassword(name: name, url: url, plainText: plainText) } - password!.updatePassword(name: cellContents["name"]!, plainText: plainText) } } diff --git a/pass/Controllers/OTPScannerController.swift b/pass/Controllers/OTPScannerController.swift index f9c71da..62b9d83 100644 --- a/pass/Controllers/OTPScannerController.swift +++ b/pass/Controllers/OTPScannerController.swift @@ -30,7 +30,7 @@ class OTPScannerController: QRScannerController { if accept == true { captureSession?.stopRunning() scannedOTP = scannedString - tempPassword = Password(name: "empty", plainText: scannedString) + tempPassword = Password(name: "empty", url: nil, plainText: scannedString) // set scannerOutput setupOneTimePasswordMessage() } else { diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 4564ab2..15269c9 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -14,7 +14,6 @@ import SVProgressHUD class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate { var passwordEntity: PasswordEntity? private var password: Password? - private var passwordCategoryText = "" private var passwordImage: UIImage? private var oneTimePasswordIndexPath : IndexPath? private var shouldPopCurrentView = false @@ -77,7 +76,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell") tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell") - passwordCategoryText = passwordEntity!.getCategoryText() let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:))) tapGesture.cancelsTouchesInView = false tableView.addGestureRecognizer(tapGesture) @@ -96,24 +94,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni let image = UIImage(data: imageData as Data) passwordImage = image } - - var passphrase = "" - if Defaults[.isRememberPassphraseOn] && self.passwordStore.pgpKeyPassphrase != nil { - passphrase = self.passwordStore.pgpKeyPassphrase! - self.decryptThenShowPassword(passphrase: passphrase) - } else { - let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in - passphrase = alert.textFields!.first!.text! - self.decryptThenShowPassword(passphrase: passphrase) - })) - alert.addTextField(configurationHandler: {(textField: UITextField!) in - textField.text = "" - textField.isSecureTextEntry = true - }) - self.present(alert, animated: true, completion: nil) - } - + self.decryptThenShowPassword() self.setupOneTimePasswordAutoRefresh() // pop the current view because this password might be "discarded" @@ -137,14 +118,33 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } } - private func decryptThenShowPassword(passphrase: String) { + private func requestPGPKeyPassphrase() -> String { + let sem = DispatchSemaphore(value: 0) + var passphrase = "" + DispatchQueue.main.async { + let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in + passphrase = alert.textFields!.first!.text! + sem.signal() + })) + alert.addTextField(configurationHandler: {(textField: UITextField!) in + textField.text = "" + textField.isSecureTextEntry = true + }) + self.present(alert, animated: true, completion: nil) + } + let _ = sem.wait(timeout: DispatchTime.distantFuture) if Defaults[.isRememberPassphraseOn] { self.passwordStore.pgpKeyPassphrase = passphrase } + return passphrase + } + + private func decryptThenShowPassword() { DispatchQueue.global(qos: .userInitiated).async { // decrypt password do { - self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)! + self.password = try self.passwordStore.decrypt(passwordEntity: self.passwordEntity!, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) } catch { DispatchQueue.main.async { let alert = UIAlertController(title: "Cannot Show Password", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.alert) @@ -215,14 +215,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni if self.password!.changed { SVProgressHUD.show(withStatus: "Saving") DispatchQueue.global(qos: .userInitiated).async { - self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: { progress in + do { + self.passwordEntity = try self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!) + } catch { DispatchQueue.main.async { - SVProgressHUD.showProgress(progress, status: "Encrypting") + Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) } - }) + } DispatchQueue.main.async { - self.passwordEntity!.synced = false - self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!) self.setTableData() self.tableView.reloadData() SVProgressHUD.showSuccess(withStatus: "Success") @@ -233,7 +233,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } @IBAction private func deletePassword(segue: UIStoryboardSegue) { - print("delete") passwordStore.delete(passwordEntity: passwordEntity!) let _ = navigationController?.popViewController(animated: true) } @@ -390,10 +389,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni // commit the change of HOTP counter if password!.changed { DispatchQueue.global(qos: .userInitiated).async { - self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: {_ in }) + do { + self.passwordEntity = try self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!) + } catch { + DispatchQueue.main.async { + Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) + } + } DispatchQueue.main.async { - self.passwordEntity!.synced = false - self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!) SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated") SVProgressHUD.dismiss(withDelay: 1) } @@ -435,7 +438,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni cell.nameLabel.text = passwordName } } - cell.categoryLabel.text = passwordCategoryText + cell.categoryLabel.text = passwordEntity!.getCategoryText() cell.selectionStyle = .none return cell case .main, .addition: @@ -465,7 +468,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni footerLabel.numberOfLines = 0 footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote) footerLabel.textColor = UIColor.gray - let dateString = self.passwordStore.getLatestUpdateInfo(filename: (passwordEntity?.path)!) + let dateString = self.passwordStore.getLatestUpdateInfo(filename: password!.url!.path) footerLabel.text = "Last Updated: \(dateString)" view.addSubview(footerLabel) return view diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index c4bc68e..d27f791 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -112,11 +112,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV SVProgressHUD.show(withStatus: "Saving") DispatchQueue.global(qos: .userInitiated).async { do { - try self.passwordStore.add(password: controller.password!, progressBlock: { progress in - DispatchQueue.main.async { - SVProgressHUD.showProgress(progress, status: "Encrypting") - } - }) + let _ = try self.passwordStore.add(password: controller.password!) DispatchQueue.main.async { // will trigger reloadTableView() by a notification SVProgressHUD.showSuccess(withStatus: "Done") @@ -323,15 +319,19 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV } let password = getPasswordEntry(by: indexPath).passwordEntity! UIImpactFeedbackGenerator(style: .medium).impactOccurred() + decryptThenCopyPassword(passwordEntity: password) + } + + + + private func requestPGPKeyPassphrase() -> String { + let sem = DispatchSemaphore(value: 0) var passphrase = "" - if Defaults[.isRememberPassphraseOn] && self.passwordStore.pgpKeyPassphrase != nil { - passphrase = self.passwordStore.pgpKeyPassphrase! - self.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase) - } else { + DispatchQueue.main.async { let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in passphrase = alert.textFields!.first!.text! - self.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase) + sem.signal() })) alert.addTextField(configurationHandler: {(textField: UITextField!) in textField.text = "" @@ -339,17 +339,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV }) self.present(alert, animated: true, completion: nil) } - + let _ = sem.wait(timeout: DispatchTime.distantFuture) + if Defaults[.isRememberPassphraseOn] { + self.passwordStore.pgpKeyPassphrase = passphrase + } + return passphrase } - private func decryptThenCopyPassword(passwordEntity: PasswordEntity, passphrase: String) { + private func decryptThenCopyPassword(passwordEntity: PasswordEntity) { SVProgressHUD.setDefaultMaskType(.black) SVProgressHUD.setDefaultStyle(.dark) SVProgressHUD.show(withStatus: "Decrypting") DispatchQueue.global(qos: .userInteractive).async { var decryptedPassword: Password? do { - decryptedPassword = try passwordEntity.decrypt(passphrase: passphrase)! + decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) DispatchQueue.main.async { Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) SVProgressHUD.showSuccess(withStatus: "Password copied, and will be cleared in 45 seconds.") diff --git a/pass/Models/Password.swift b/pass/Models/Password.swift index dd5106d..c6c99e4 100644 --- a/pass/Models/Password.swift +++ b/pass/Models/Password.swift @@ -20,6 +20,15 @@ class Password { static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter", "otpauth"] var name = "" + var url: URL? + var namePath: String { + get { + if url == nil { + return "" + } + return url!.deletingPathExtension().path + } + } var password = "" var additions = [String: String]() var additionKeys = [String]() @@ -47,19 +56,20 @@ class Password { } } - init(name: String, plainText: String) { - self.initEverything(name: name, plainText: plainText) + init(name: String, url: URL?, plainText: String) { + self.initEverything(name: name, url: url, plainText: plainText) } - func updatePassword(name: String, plainText: String) { - if self.plainText != plainText { - self.initEverything(name: name, plainText: plainText) + func updatePassword(name: String, url: URL?, plainText: String) { + if self.plainText != plainText || self.url != url { + self.initEverything(name: name, url: url, plainText: plainText) changed = true } } - private func initEverything(name: String, plainText: String) { + private func initEverything(name: String, url: URL?, plainText: String) { self.name = name + self.url = url self.plainText = plainText self.additions.removeAll() self.additionKeys.removeAll() @@ -322,7 +332,7 @@ class Password { if newOtpauth != nil { lines.append(newOtpauth!) } - self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n")) + self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n")) // get and return the password return self.otpToken?.currentPassword diff --git a/pass/Models/PasswordEntity.swift b/pass/Models/PasswordEntity.swift index f65d1e7..143d63e 100644 --- a/pass/Models/PasswordEntity.swift +++ b/pass/Models/PasswordEntity.swift @@ -21,24 +21,6 @@ extension PasswordEntity { } } - func decrypt(passphrase: String) throws -> Password? { - var password: Password? - let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(path!)") - let encryptedData = try Data(contentsOf: encryptedDataPath) - let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase) - let plainText = String(data: decryptedData, encoding: .utf8) ?? "" - password = Password(name: name!, plainText: plainText) - return password - } - - func encrypt(password: Password) throws -> Data { - name = password.name - let plainData = password.getPlainData() - let pgp = PasswordStore.shared.pgp - let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored]) - return encryptedData - } - func getCategoryText() -> String { var parentEntity = parent var passwordCategoryArray: [String] = [] diff --git a/pass/Models/PasswordStore.swift b/pass/Models/PasswordStore.swift index e7d3bce..a57dc00 100644 --- a/pass/Models/PasswordStore.swift +++ b/pass/Models/PasswordStore.swift @@ -284,10 +284,9 @@ class PasswordStore { } func passwordExisted(password: Password) -> Bool { - print(password.name) let passwordEntityFetchRequest = NSFetchRequest(entityName: "PasswordEntity") do { - passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@", password.name) + passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@ and path = %@", password.name, password.url!.path) let count = try context.count(for: passwordEntityFetchRequest) if count > 0 { return true @@ -300,6 +299,32 @@ class PasswordStore { return true } + func passwordEntityExisted(path: String) -> Bool { + let passwordEntityFetchRequest = NSFetchRequest(entityName: "PasswordEntity") + do { + passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@", path) + let count = try context.count(for: passwordEntityFetchRequest) + if count > 0 { + return true + } else { + return false + } + } catch { + fatalError("Failed to fetch password entities: \(error)") + } + return true + } + + func getPasswordEntity(by path: String) -> PasswordEntity? { + let passwordEntityFetchRequest = NSFetchRequest(entityName: "PasswordEntity") + do { + passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@", path) + return try context.fetch(passwordEntityFetchRequest).first as? PasswordEntity + } catch { + fatalError("Failed to fetch password entities: \(error)") + } + } + func cloneRepository(remoteRepoURL: URL, credential: GitCredential, transferProgressBlock: @escaping (UnsafePointer, UnsafeMutablePointer) -> Void, @@ -511,19 +536,17 @@ class PasswordStore { func updateRemoteRepo() { } - func createAddCommitInRepository(message: String, fileData: Data, filename: String, progressBlock: (_ progress: Float) -> Void) -> GTCommit? { + func createAddCommitInRepository(message: String, path: String) -> GTCommit? { do { - try storeRepository?.index().add(fileData, withPath: filename) + try storeRepository?.index().addFile(path) try storeRepository?.index().write() let newTree = try storeRepository!.index().writeTree() let headReference = try storeRepository!.headReference() let commitEnum = try GTEnumerator(repository: storeRepository!) try commitEnum.pushSHA(headReference.targetOID.sha!) let parent = commitEnum.nextObject() as! GTCommit - progressBlock(0.5) let signature = gitSignatureForNow let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name) - progressBlock(0.7) return commit } catch { print(error) @@ -571,57 +594,93 @@ class PasswordStore { try storeRepository?.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock) } - func add(password: Password, progressBlock: (_ progress: Float) -> Void) throws { - progressBlock(0.0) + private func addPasswordEntities(password: Password) -> PasswordEntity? { + var passwordURL = password.url! + var paths: [String] = [] + while passwordURL.path != "." { + paths.append(passwordURL.path) + passwordURL = passwordURL.deletingLastPathComponent() + } + paths.reverse() + var parentPasswordEntity: PasswordEntity? = nil + for path in paths { + if let passwordEntity = getPasswordEntity(by: path) { + parentPasswordEntity = passwordEntity + } else { + if path.hasSuffix(".gpg") { + return insertPasswordEntity(name: URL(string: path)!.deletingPathExtension().lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: false) + } else { + parentPasswordEntity = insertPasswordEntity(name: URL(string: path)!.lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: true) + let fm = FileManager.default + let saveURL = storeURL.appendingPathComponent(path) + do { + try fm.createDirectory(at: saveURL, withIntermediateDirectories: false, attributes: nil) + } catch { + print(error) + } + } + } + } + return nil + } + + private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? { + var ret: PasswordEntity? = nil + DispatchQueue.main.sync { + if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity { + passwordEntity.name = name + passwordEntity.path = path + passwordEntity.parent = parent + passwordEntity.synced = synced + passwordEntity.isDir = isDir + do { + try self.context.save() + ret = passwordEntity + } catch { + fatalError("Failed to insert a PasswordEntity: \(error)") + } + } + } + return ret + } + + func add(password: Password) throws -> PasswordEntity? { guard !passwordExisted(password: password) else { throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot add password: password duplicated."]) } - let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity - do { - let encryptedData = try passwordEntity.encrypt(password: password) - progressBlock(0.3) - let saveURL = storeURL.appendingPathComponent("\(password.name).gpg") - try encryptedData.write(to: saveURL) - passwordEntity.name = password.name - passwordEntity.path = "\(password.name).gpg" - passwordEntity.parent = nil - passwordEntity.synced = false - passwordEntity.isDir = false - try context.save() - print(saveURL.path) - let _ = createAddCommitInRepository(message: "Add password for \(passwordEntity.nameWithCategory) to store using Pass for iOS.", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock) - progressBlock(1.0) - NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) - } catch { - print(error) - } + let newPasswordEntity = addPasswordEntities(password: password) + print("new: \(newPasswordEntity!.path!)") + let saveURL = storeURL.appendingPathComponent(password.url!.path) + try self.encrypt(password: password).write(to: saveURL) + let _ = createAddCommitInRepository(message: "Add password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.", path: password.url!.path) + NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) + return newPasswordEntity } - func update(passwordEntity: PasswordEntity, password: Password, progressBlock: (_ progress: Float) -> Void) { - progressBlock(0.0) - do { - let encryptedData = try passwordEntity.encrypt(password: password) - let saveURL = storeURL.appendingPathComponent(passwordEntity.path!) - try encryptedData.write(to: saveURL) - progressBlock(0.3) - let _ = createAddCommitInRepository(message: "Edit password for \(passwordEntity.nameWithCategory) using Pass for iOS.", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock) - progressBlock(1.0) - NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) - } catch { - print(error) - } + func update(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? { + delete(passwordEntity: passwordEntity) + return try add(password: password) } + public func delete(passwordEntity: PasswordEntity) { - Utils.removeFileIfExists(at: storeURL.appendingPathComponent(passwordEntity.path!)) - let _ = createRemoveCommitInRepository(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS", path: passwordEntity.path!) - context.delete(passwordEntity) - do { - try context.save() - } catch { - fatalError("Failed to delete a PasswordEntity: \(error)") + DispatchQueue.main.async { + let _ = self.createRemoveCommitInRepository(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS", path: passwordEntity.path!) + var current: PasswordEntity? = passwordEntity + while current != nil && (current!.children!.count == 0 || !current!.isDir) { + Utils.removeFileIfExists(at: self.storeURL.appendingPathComponent(current!.path!)) + let parent = current!.parent + self.context.delete(current!) + current = parent + do { + try self.context.save() + } catch { + fatalError("Failed to delete a PasswordEntity: \(error)") + } + } + NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) + } - NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) } func saveUpdated(passwordEntity: PasswordEntity) { @@ -741,4 +800,27 @@ class PasswordStore { // get a list of local commits return try storeRepository?.localCommitsRelative(toRemoteBranch: remoteMasterBranch) } + + + + func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? { + var password: Password? + let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(passwordEntity.path!)") + let encryptedData = try Data(contentsOf: encryptedDataPath) + var passphrase = self.pgpKeyPassphrase + if passphrase == nil { + passphrase = requestPGPKeyPassphrase() + } + let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase) + let plainText = String(data: decryptedData, encoding: .utf8) ?? "" + password = Password(name: passwordEntity.name!, url: URL(string: passwordEntity.path!), plainText: plainText) + return password + } + + func encrypt(password: Password) throws -> Data { + let plainData = password.getPlainData() + let pgp = PasswordStore.shared.pgp + let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored]) + return encryptedData + } }