Fix fail-safe mechanism for other decryption scenarios
This commit is contained in:
parent
3e114daca1
commit
fcc8961e46
15 changed files with 153 additions and 100 deletions
|
|
@ -95,7 +95,7 @@
|
||||||
556EC3DA22335D3400934F9C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30C25DBF21F3599E00BB27BB /* InfoPlist.strings */; };
|
556EC3DA22335D3400934F9C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30C25DBF21F3599E00BB27BB /* InfoPlist.strings */; };
|
||||||
556EC3DB22335D3D00934F9C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30C25DBF21F3599E00BB27BB /* InfoPlist.strings */; };
|
556EC3DB22335D3D00934F9C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30C25DBF21F3599E00BB27BB /* InfoPlist.strings */; };
|
||||||
8BA607EB4C9C8258741AC18C /* Pods_passExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14E955B67C88672AA3A40BA0 /* Pods_passExtension.framework */; };
|
8BA607EB4C9C8258741AC18C /* Pods_passExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14E955B67C88672AA3A40BA0 /* Pods_passExtension.framework */; };
|
||||||
9A652412244ABED400DA0A41 /* UIAlertActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A652411244ABED400DA0A41 /* UIAlertActionExtension.swift */; };
|
9A652414244BB33300DA0A41 /* UIAlertActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A652413244BB33300DA0A41 /* UIAlertActionExtension.swift */; };
|
||||||
9A8A8387402FCCCECB1232A4 /* Pods_passKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2B2F844061EFA534FE9506 /* Pods_passKitTests.framework */; };
|
9A8A8387402FCCCECB1232A4 /* Pods_passKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2B2F844061EFA534FE9506 /* Pods_passKitTests.framework */; };
|
||||||
9AA710CA23939C68009E3213 /* GitCredentialPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA710C923939C68009E3213 /* GitCredentialPassword.swift */; };
|
9AA710CA23939C68009E3213 /* GitCredentialPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA710C923939C68009E3213 /* GitCredentialPassword.swift */; };
|
||||||
9ADC954124418A5F0005402E /* PasswordStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADC954024418A5F0005402E /* PasswordStoreTest.swift */; };
|
9ADC954124418A5F0005402E /* PasswordStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADC954024418A5F0005402E /* PasswordStoreTest.swift */; };
|
||||||
|
|
@ -348,7 +348,7 @@
|
||||||
62DEE9943E0F2B8C79E3FC5B /* Pods-passExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.release.xcconfig"; sourceTree = "<group>"; };
|
62DEE9943E0F2B8C79E3FC5B /* Pods-passExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
64AA8DF9E73F39CCC3317247 /* Pods-passKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.release.xcconfig"; sourceTree = "<group>"; };
|
64AA8DF9E73F39CCC3317247 /* Pods-passKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
7CAD21E487234A0631B52E20 /* Pods-passKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.debug.xcconfig"; sourceTree = "<group>"; };
|
7CAD21E487234A0631B52E20 /* Pods-passKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
9A652411244ABED400DA0A41 /* UIAlertActionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertActionExtension.swift; sourceTree = "<group>"; };
|
9A652413244BB33300DA0A41 /* UIAlertActionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertActionExtension.swift; sourceTree = "<group>"; };
|
||||||
9AA710C923939C68009E3213 /* GitCredentialPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCredentialPassword.swift; sourceTree = "<group>"; };
|
9AA710C923939C68009E3213 /* GitCredentialPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitCredentialPassword.swift; sourceTree = "<group>"; };
|
||||||
9ADC954024418A5F0005402E /* PasswordStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordStoreTest.swift; sourceTree = "<group>"; };
|
9ADC954024418A5F0005402E /* PasswordStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordStoreTest.swift; sourceTree = "<group>"; };
|
||||||
A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurePasteboard.swift; sourceTree = "<group>"; };
|
A20691F31F2A3D0E0096483D /* SecurePasteboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurePasteboard.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -590,6 +590,7 @@
|
||||||
30697C3721F63C990064FCAC /* UITextFieldExtension.swift */,
|
30697C3721F63C990064FCAC /* UITextFieldExtension.swift */,
|
||||||
30697C3521F63C990064FCAC /* UIViewControllerExtension.swift */,
|
30697C3521F63C990064FCAC /* UIViewControllerExtension.swift */,
|
||||||
30697C3821F63C990064FCAC /* UIViewExtension.swift */,
|
30697C3821F63C990064FCAC /* UIViewExtension.swift */,
|
||||||
|
9A652413244BB33300DA0A41 /* UIAlertActionExtension.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -650,14 +651,6 @@
|
||||||
path = Crypto;
|
path = Crypto;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
9A652410244ABEB800DA0A41 /* Extensions */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
9A652411244ABED400DA0A41 /* UIAlertActionExtension.swift */,
|
|
||||||
);
|
|
||||||
path = Extensions;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
A2168A801EFD431A005EA873 /* Controllers */ = {
|
A2168A801EFD431A005EA873 /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -896,7 +889,6 @@
|
||||||
DC917BD51E2E8231000FDF54 /* pass */ = {
|
DC917BD51E2E8231000FDF54 /* pass */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
9A652410244ABEB800DA0A41 /* Extensions */,
|
|
||||||
DC19400C1E4B39400077E0A3 /* Controllers */,
|
DC19400C1E4B39400077E0A3 /* Controllers */,
|
||||||
DC19400E1E4B3A610077E0A3 /* Helpers */,
|
DC19400E1E4B3A610077E0A3 /* Helpers */,
|
||||||
DC19400F1E4B3A9E0077E0A3 /* Views */,
|
DC19400F1E4B3A9E0077E0A3 /* Views */,
|
||||||
|
|
@ -1449,6 +1441,7 @@
|
||||||
30A1D2A221B2BC6F00E2D1F7 /* TokenBuilder.swift in Sources */,
|
30A1D2A221B2BC6F00E2D1F7 /* TokenBuilder.swift in Sources */,
|
||||||
30697C3C21F63C990064FCAC /* UITextFieldExtension.swift in Sources */,
|
30697C3C21F63C990064FCAC /* UITextFieldExtension.swift in Sources */,
|
||||||
302E85632125EE550031BA64 /* Constants.swift in Sources */,
|
302E85632125EE550031BA64 /* Constants.swift in Sources */,
|
||||||
|
9A652414244BB33300DA0A41 /* UIAlertActionExtension.swift in Sources */,
|
||||||
301F6463216162550071A4CE /* AdditionField.swift in Sources */,
|
301F6463216162550071A4CE /* AdditionField.swift in Sources */,
|
||||||
30697C3021F63C5A0064FCAC /* AppError.swift in Sources */,
|
30697C3021F63C5A0064FCAC /* AppError.swift in Sources */,
|
||||||
30697C2B21F63C5A0064FCAC /* Globals.swift in Sources */,
|
30697C2B21F63C5A0064FCAC /* Globals.swift in Sources */,
|
||||||
|
|
@ -1541,7 +1534,6 @@
|
||||||
DC037CBF1E4ED4E100609409 /* TextViewTableViewCell.swift in Sources */,
|
DC037CBF1E4ED4E100609409 /* TextViewTableViewCell.swift in Sources */,
|
||||||
DCC441541E916382008A90C4 /* SSHKeyArmorImportTableViewController.swift in Sources */,
|
DCC441541E916382008A90C4 /* SSHKeyArmorImportTableViewController.swift in Sources */,
|
||||||
306D970E24091CDD006C0E2E /* SwitchTableViewCell.swift in Sources */,
|
306D970E24091CDD006C0E2E /* SwitchTableViewCell.swift in Sources */,
|
||||||
9A652412244ABED400DA0A41 /* UIAlertActionExtension.swift in Sources */,
|
|
||||||
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */,
|
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */,
|
||||||
DC8963C01E38EEB900828B09 /* SSHKeyUrlImportTableViewController.swift in Sources */,
|
DC8963C01E38EEB900828B09 /* SSHKeyUrlImportTableViewController.swift in Sources */,
|
||||||
3066AD6823EE0D6500F65535 /* PGPKeyImporter.swift in Sources */,
|
3066AD6823EE0D6500F65535 /* PGPKeyImporter.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
if self.shouldPopCurrentView {
|
if self.shouldPopCurrentView {
|
||||||
let alert = UIAlertController(title: "Notice".localize(), message: "PreviousChangesDiscarded.".localize(), preferredStyle: UIAlertController.Style.alert)
|
let alert = UIAlertController(title: "Notice".localize(), message: "PreviousChangesDiscarded.".localize(), preferredStyle: UIAlertController.Style.alert)
|
||||||
alert.addAction(UIAlertAction(title: "Ok".localize(), style: UIAlertAction.Style.default, handler: {_ in
|
alert.addAction(UIAlertAction.okAndPopView(controller: self))
|
||||||
_ = self.navigationController?.popViewController(animated: true)
|
|
||||||
}))
|
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +97,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
do {
|
do {
|
||||||
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
||||||
self.password = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
self.password = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
|
self.showPassword()
|
||||||
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
// alert: cancel or try again
|
// alert: cancel or try again
|
||||||
|
|
@ -111,7 +110,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
|
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
// alert: cancel or try again
|
// alert: cancel or try again
|
||||||
|
|
@ -122,10 +120,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
})
|
})
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// display password
|
|
||||||
self.showPassword()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
|
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
|
||||||
}
|
}
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
|
let cancelAction = UIAlertAction.cancel()
|
||||||
|
|
||||||
ac.addAction(allAction)
|
ac.addAction(allAction)
|
||||||
ac.addAction(unsyncedAction)
|
ac.addAction(unsyncedAction)
|
||||||
|
|
@ -453,32 +453,49 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
|
|
||||||
private func decryptThenCopyPassword(from indexPath: IndexPath) {
|
private func decryptThenCopyPassword(from indexPath: IndexPath) {
|
||||||
guard PGPAgent.shared.isPrepared else {
|
guard PGPAgent.shared.isPrepared else {
|
||||||
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
|
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
|
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
SVProgressHUD.dismiss()
|
SVProgressHUD.dismiss()
|
||||||
|
self.decryptPassword(passwordEntity: passwordEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
var decryptedPassword: Password?
|
|
||||||
do {
|
do {
|
||||||
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
||||||
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
let decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
SecurePasteboard.shared.copy(textToCopy: decryptedPassword?.password)
|
SecurePasteboard.shared.copy(textToCopy: decryptedPassword.password)
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
SVProgressHUD.setDefaultStyle(.dark)
|
SVProgressHUD.setDefaultStyle(.dark)
|
||||||
SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize())
|
SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize())
|
||||||
SVProgressHUD.dismiss(withDelay: 0.6)
|
SVProgressHUD.dismiss(withDelay: 0.6)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
// alert: cancel or try again
|
||||||
|
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||||
|
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
||||||
|
self.decryptPassword(passwordEntity: passwordEntity, keyID: action.title)
|
||||||
|
}
|
||||||
|
alert.addAction(selectKey)
|
||||||
|
|
||||||
|
self.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func generateSections(item: [PasswordTableEntry]) {
|
private func generateSections(item: [PasswordTableEntry]) {
|
||||||
let collation = UILocalizedIndexedCollation.current()
|
let collation = UILocalizedIndexedCollation.current()
|
||||||
let sectionTitles = collation.sectionIndexTitles
|
let sectionTitles = collation.sectionIndexTitles
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
//
|
|
||||||
// UIAlertActionExtension.swift
|
|
||||||
// passKit
|
|
||||||
//
|
|
||||||
// Created by Sun, Mingshen on 4/17/20.
|
|
||||||
// Copyright © 2020 Bob Sun. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Foundation
|
|
||||||
import passKit
|
|
||||||
|
|
||||||
extension UIAlertAction {
|
|
||||||
static func cancelAndPopView(controller: UIViewController) -> UIAlertAction {
|
|
||||||
UIAlertAction(title: "Cancel".localize(), style: .cancel) { _ in
|
|
||||||
controller.navigationController?.popViewController(animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func cancel() -> UIAlertAction {
|
|
||||||
cancel(with: "Cancel")
|
|
||||||
}
|
|
||||||
|
|
||||||
static func dismiss() -> UIAlertAction {
|
|
||||||
cancel(with: "Dismiss")
|
|
||||||
}
|
|
||||||
|
|
||||||
static func cancel(with title: String) -> UIAlertAction {
|
|
||||||
UIAlertAction(title: "Cancel".localize(), style: .cancel, handler: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func selectKey(controller: UIViewController, handler: ((UIAlertAction) -> Void)?) -> UIAlertAction {
|
|
||||||
UIAlertAction(title: "Select Key", style: .default) { _ in
|
|
||||||
let selectKeyAlert = UIAlertController(title: "Select from imported keys", message: nil, preferredStyle: .actionSheet)
|
|
||||||
try? PGPAgent.shared.getShortKeyID().forEach({ k in
|
|
||||||
let action = UIAlertAction(title: k, style: .default, handler: handler)
|
|
||||||
selectKeyAlert.addAction(action)
|
|
||||||
})
|
|
||||||
selectKeyAlert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
|
|
||||||
controller.present(selectKeyAlert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -31,11 +31,11 @@ public func requestGitCredentialPassword(credential: GitCredential.Credential,
|
||||||
$0.text = lastPassword ?? ""
|
$0.text = lastPassword ?? ""
|
||||||
$0.isSecureTextEntry = true
|
$0.isSecureTextEntry = true
|
||||||
}
|
}
|
||||||
alert.addAction(UIAlertAction(title: "Ok".localize(), style: .default) { _ in
|
alert.addAction(UIAlertAction.ok() { _ in
|
||||||
password = alert.textFields?.first?.text
|
password = alert.textFields?.first?.text
|
||||||
sem.signal()
|
sem.signal()
|
||||||
})
|
})
|
||||||
alert.addAction(UIAlertAction(title: "Cancel".localize(), style: .cancel) { _ in
|
alert.addAction(UIAlertAction.cancel() { _ in
|
||||||
password = nil
|
password = nil
|
||||||
sem.signal()
|
sem.signal()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -139,20 +139,35 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
|
|
||||||
let passwordEntity = entry.passwordEntity
|
let passwordEntity = entry.passwordEntity
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
self.decryptPassword(passwordEntity: passwordEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
var decryptedPassword: Password?
|
|
||||||
do {
|
do {
|
||||||
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
||||||
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
let decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
let username = decryptedPassword?.username ?? decryptedPassword?.login ?? ""
|
|
||||||
let password = decryptedPassword?.password ?? ""
|
let username = decryptedPassword.getUsernameForCompletion()
|
||||||
DispatchQueue.main.async {// prepare a dictionary to return
|
let password = decryptedPassword.password
|
||||||
|
DispatchQueue.main.async {
|
||||||
let passwordCredential = ASPasswordCredential(user: username, password: password)
|
let passwordCredential = ASPasswordCredential(user: username, password: password)
|
||||||
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
|
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential)
|
||||||
|
}
|
||||||
|
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||||
|
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
||||||
|
self.decryptPassword(passwordEntity: passwordEntity, keyID: action.title)
|
||||||
|
}
|
||||||
|
alert.addAction(selectKey)
|
||||||
|
|
||||||
|
self.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,36 +147,52 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
|
|
||||||
let passwordEntity = entry.passwordEntity
|
let passwordEntity = entry.passwordEntity
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
self.decryptPassword(passwordEntity: passwordEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
var decryptedPassword: Password?
|
|
||||||
do {
|
do {
|
||||||
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
||||||
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
let decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
let username = decryptedPassword?.username ?? decryptedPassword?.login ?? decryptedPassword?.nameFromPath ?? ""
|
|
||||||
let password = decryptedPassword?.password ?? ""
|
let username = decryptedPassword.getUsernameForCompletion()
|
||||||
|
let password = decryptedPassword.password
|
||||||
DispatchQueue.main.async {// prepare a dictionary to return
|
DispatchQueue.main.async {// prepare a dictionary to return
|
||||||
switch self.extensionAction {
|
switch self.extensionAction {
|
||||||
case .findLogin:
|
case .findLogin:
|
||||||
let extensionItem = NSExtensionItem()
|
let extensionItem = NSExtensionItem()
|
||||||
var returnDictionary = [OnePasswordExtensionKey.usernameKey: username,
|
var returnDictionary = [OnePasswordExtensionKey.usernameKey: username,
|
||||||
OnePasswordExtensionKey.passwordKey: password]
|
OnePasswordExtensionKey.passwordKey: password]
|
||||||
if let totpPassword = decryptedPassword?.currentOtp {
|
if let totpPassword = decryptedPassword.currentOtp {
|
||||||
returnDictionary[OnePasswordExtensionKey.totpKey] = totpPassword
|
returnDictionary[OnePasswordExtensionKey.totpKey] = totpPassword
|
||||||
}
|
}
|
||||||
extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))]
|
extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))]
|
||||||
self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil)
|
self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil)
|
||||||
case .fillBrowser:
|
case .fillBrowser:
|
||||||
Utils.copyToPasteboard(textToCopy: decryptedPassword?.password)
|
Utils.copyToPasteboard(textToCopy: decryptedPassword.password)
|
||||||
// return a dictionary for JavaScript for best-effor fill in
|
// return a dictionary for JavaScript for best-effor fill in
|
||||||
let extensionItem = NSExtensionItem()
|
let extensionItem = NSExtensionItem()
|
||||||
let returnDictionary = [NSExtensionJavaScriptFinalizeArgumentKey : ["username": username, "password": password]]
|
let returnDictionary = [NSExtensionJavaScriptFinalizeArgumentKey : ["username": username, "password": password]]
|
||||||
extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))]
|
extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))]
|
||||||
self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil)
|
self.extensionContext?.completeRequest(returningItems: [extensionItem], completionHandler: nil)
|
||||||
default:
|
default:
|
||||||
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
|
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
// alert: cancel or try again
|
||||||
|
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||||
|
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
||||||
|
self.decryptPassword(passwordEntity: passwordEntity, keyID: action.title)
|
||||||
|
}
|
||||||
|
alert.addAction(selectKey)
|
||||||
|
|
||||||
|
self.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: "Dismiss".localize(), style: .cancel, handler: nil))
|
alert.addAction(UIAlertAction.dismiss())
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,20 @@ public class PGPAgent {
|
||||||
return pgpInterface?.shortKeyID.sorted() ?? []
|
return pgpInterface?.shortKeyID.sorted() ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
public func decrypt(encryptedData: Data, keyID: String, requestPGPKeyPassphrase: (String) -> String) throws -> Data? {
|
public func decrypt(encryptedData: Data, keyID: String, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Data? {
|
||||||
// Init keys.
|
// Init keys.
|
||||||
try checkAndInit()
|
try checkAndInit()
|
||||||
guard let pgpInterface = pgpInterface else {
|
guard let pgpInterface = pgpInterface else {
|
||||||
throw AppError.Decryption
|
throw AppError.Decryption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var keyID = keyID;
|
||||||
if !pgpInterface.containsPrivateKey(with: keyID) {
|
if !pgpInterface.containsPrivateKey(with: keyID) {
|
||||||
throw AppError.PgpPrivateKeyNotFound(keyID: keyID)
|
if pgpInterface.keyID.count == 1 {
|
||||||
|
keyID = pgpInterface.keyID.first!
|
||||||
|
} else {
|
||||||
|
throw AppError.PgpPrivateKeyNotFound(keyID: keyID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember the previous status and set the current status
|
// Remember the previous status and set the current status
|
||||||
|
|
|
||||||
53
passKit/Extensions/UIAlertActionExtension.swift
Normal file
53
passKit/Extensions/UIAlertActionExtension.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// UIAlertActionExtension.swift
|
||||||
|
// passKit
|
||||||
|
//
|
||||||
|
// Created by Sun, Mingshen on 4/17/20.
|
||||||
|
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension UIAlertAction {
|
||||||
|
public static func cancelAndPopView(controller: UIViewController) -> UIAlertAction {
|
||||||
|
return cancel() { _ in
|
||||||
|
controller.navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func cancel(handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
|
||||||
|
cancel(with: "Cancel", handler: handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func dismiss(handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
|
||||||
|
cancel(with: "Dismiss", handler: handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func cancel(with title: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
|
||||||
|
UIAlertAction(title: "Cancel".localize(), style: .cancel, handler: handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ok(handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
|
||||||
|
UIAlertAction(title: "Ok".localize(), style: .default, handler: handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func okAndPopView(controller: UIViewController) -> UIAlertAction {
|
||||||
|
return ok() { _ in
|
||||||
|
controller.navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func selectKey(controller: UIViewController, handler: ((UIAlertAction) -> Void)?) -> UIAlertAction {
|
||||||
|
UIAlertAction(title: "Select Key", style: .default) { _ in
|
||||||
|
let selectKeyAlert = UIAlertController(title: "Select from imported keys", message: nil, preferredStyle: .actionSheet)
|
||||||
|
try? PGPAgent.shared.getShortKeyID().forEach({ k in
|
||||||
|
let action = UIAlertAction(title: k, style: .default, handler: handler)
|
||||||
|
selectKeyAlert.addAction(action)
|
||||||
|
})
|
||||||
|
selectKeyAlert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
|
||||||
|
controller.present(selectKeyAlert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -47,15 +47,15 @@ public class Utils {
|
||||||
let title = "Passphrase".localize() + " (\(keyID.suffix(8)))"
|
let title = "Passphrase".localize() + " (\(keyID.suffix(8)))"
|
||||||
let message = "FillInPgpPassphrase.".localize()
|
let message = "FillInPgpPassphrase.".localize()
|
||||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
alert.addAction(UIAlertAction(title: "Ok".localize(), style: UIAlertAction.Style.default, handler: {_ in
|
alert.addAction(UIAlertAction.ok() { _ in
|
||||||
passphrase = alert.textFields?.first?.text ?? ""
|
passphrase = alert.textFields?.first?.text ?? ""
|
||||||
sem.signal()
|
sem.signal()
|
||||||
}))
|
})
|
||||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
alert.addTextField() { textField in
|
||||||
textField.text = AppKeychain.shared.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? ""
|
textField.text = AppKeychain.shared.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? ""
|
||||||
textField.isSecureTextEntry = true
|
textField.isSecureTextEntry = true
|
||||||
})
|
}
|
||||||
controller.present(alert, animated: true, completion: nil)
|
controller.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||||
if Defaults.isRememberPGPPassphraseOn {
|
if Defaults.isRememberPGPPassphraseOn {
|
||||||
|
|
|
||||||
|
|
@ -213,4 +213,8 @@ public class Password {
|
||||||
// get and return the password
|
// get and return the password
|
||||||
return self.otpToken?.currentPassword
|
return self.otpToken?.currentPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getUsernameForCompletion() -> String {
|
||||||
|
username ?? login ?? nameFromPath ?? ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -698,7 +698,7 @@ public class PasswordStore {
|
||||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func decrypt(passwordEntity: PasswordEntity, keyID: String? = nil, requestPGPKeyPassphrase: (String) -> String) throws -> Password? {
|
public func decrypt(passwordEntity: PasswordEntity, keyID: String? = nil, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Password {
|
||||||
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||||
let keyID = keyID ?? findGPGID(from: encryptedDataPath)
|
let keyID = keyID ?? findGPGID(from: encryptedDataPath)
|
||||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class PGPAgentTest: XCTestCase {
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func basicEncryptDecrypt(using pgpAgent: PGPAgent, keyID: String, encryptKeyID: String? = nil, requestPassphrase: (String) -> String = requestPGPKeyPassphrase, encryptInArmored: Bool = true, encryptInArmoredNow: Bool = true) throws -> Data? {
|
func basicEncryptDecrypt(using pgpAgent: PGPAgent, keyID: String, encryptKeyID: String? = nil, requestPassphrase: @escaping (String) -> String = requestPGPKeyPassphrase, encryptInArmored: Bool = true, encryptInArmoredNow: Bool = true) throws -> Data? {
|
||||||
passKit.Defaults.encryptInArmored = encryptInArmored
|
passKit.Defaults.encryptInArmored = encryptInArmored
|
||||||
let encryptedData = try pgpAgent.encrypt(plainData: testData, keyID: keyID)
|
let encryptedData = try pgpAgent.encrypt(plainData: testData, keyID: keyID)
|
||||||
passKit.Defaults.encryptInArmored = encryptInArmoredNow
|
passKit.Defaults.encryptInArmored = encryptInArmoredNow
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class PasswordStoreTest: XCTestCase {
|
||||||
|
|
||||||
let testPassword = Password(name: "test", url: URL(string: "test.gpg")!, plainText: "testpassword")
|
let testPassword = Password(name: "test", url: URL(string: "test.gpg")!, plainText: "testpassword")
|
||||||
let testPasswordEntity = try passwordStore.add(password: testPassword)!
|
let testPasswordEntity = try passwordStore.add(password: testPassword)!
|
||||||
let testPasswordPlain = try passwordStore.decrypt(passwordEntity: testPasswordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase )!
|
let testPasswordPlain = try passwordStore.decrypt(passwordEntity: testPasswordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase )
|
||||||
XCTAssertEqual(testPasswordPlain.plainText, "testpassword")
|
XCTAssertEqual(testPasswordPlain.plainText, "testpassword")
|
||||||
|
|
||||||
passwordStore.erase()
|
passwordStore.erase()
|
||||||
|
|
@ -65,7 +65,7 @@ class PasswordStoreTest: XCTestCase {
|
||||||
|
|
||||||
private func decrypt(passwordStore: PasswordStore, path: String, passphrase: String) throws -> Password {
|
private func decrypt(passwordStore: PasswordStore, path: String, passphrase: String) throws -> Password {
|
||||||
let entity = passwordStore.getPasswordEntity(by: path, isDir: false)!
|
let entity = passwordStore.getPasswordEntity(by: path, isDir: false)!
|
||||||
return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase )!
|
return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue