Add switch to turn on/off remembering passphrase

If the switch is on, users need  to type passphrase of secret key once. The key will be stored in the Keychain.

If the switch is off, users have to fill in passphrase for each decryption including show password detail and long press to copy.
This commit is contained in:
Bob Sun 2017-02-28 12:25:52 +08:00
parent 1c45766b96
commit fa512e6c86
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
10 changed files with 138 additions and 37 deletions

View file

@ -11,7 +11,21 @@ import SwiftyUserDefaults
class GeneralSettingsTableViewController: BasicStaticTableViewController { class GeneralSettingsTableViewController: BasicStaticTableViewController {
let hideUnknownSwitch = UISwitch() let hideUnknownSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
uiSwitch.sizeToFit()
uiSwitch.addTarget(self, action: #selector(hideUnknownSwitchAction(_:)), for: UIControlEvents.valueChanged)
return uiSwitch
}()
let rememberPassphraseSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
uiSwitch.sizeToFit()
uiSwitch.addTarget(self, action: #selector(rememberPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged)
uiSwitch.isOn = Defaults[.isRememberPassphraseOn]
return uiSwitch
}()
override func viewDidLoad() { override func viewDidLoad() {
navigationItemTitle = "General" navigationItemTitle = "General"
@ -20,7 +34,10 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],], [[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],
// section 1 // section 1
[[.title: "Hide Unknown Fields", .action: "none",],], [
[.title: "Remember Phassphrase", .action: "none",],
[.title: "Hide Unknown Fields", .action: "none",],
],
] ]
super.viewDidLoad() super.viewDidLoad()
@ -31,8 +48,6 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
let cell = super.tableView(tableView, cellForRowAt: indexPath) let cell = super.tableView(tableView, cellForRowAt: indexPath)
if cell.textLabel?.text == "Hide Unknown Fields" { if cell.textLabel?.text == "Hide Unknown Fields" {
cell.accessoryType = .none cell.accessoryType = .none
hideUnknownSwitch.onTintColor = UIColor(displayP3Red: 0, green: 122.0/255, blue: 1, alpha: 1)
hideUnknownSwitch.sizeToFit()
let detailButton = UIButton(type: .detailDisclosure) let detailButton = UIButton(type: .detailDisclosure)
hideUnknownSwitch.frame = CGRect(x: detailButton.bounds.width+10, y: 0, width: hideUnknownSwitch.bounds.width, height: hideUnknownSwitch.bounds.height) hideUnknownSwitch.frame = CGRect(x: detailButton.bounds.width+10, y: 0, width: hideUnknownSwitch.bounds.width, height: hideUnknownSwitch.bounds.height)
detailButton.frame = CGRect(x: 0, y: 5, width: detailButton.bounds.width, height: detailButton.bounds.height) detailButton.frame = CGRect(x: 0, y: 5, width: detailButton.bounds.width, height: detailButton.bounds.height)
@ -42,8 +57,11 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
accessoryView.addSubview(hideUnknownSwitch) accessoryView.addSubview(hideUnknownSwitch)
cell.accessoryView = accessoryView cell.accessoryView = accessoryView
cell.selectionStyle = .none cell.selectionStyle = .none
hideUnknownSwitch.addTarget(self, action: #selector(hideUnknownSwitchAction(_:)), for: UIControlEvents.valueChanged)
hideUnknownSwitch.isOn = Defaults[.isHideUnknownOn] hideUnknownSwitch.isOn = Defaults[.isHideUnknownOn]
} else if cell.textLabel?.text == "Remember Phassphrase" {
cell.accessoryType = .none
cell.selectionStyle = .none
cell.accessoryView = rememberPassphraseSwitch
} }
return cell return cell
} }
@ -58,4 +76,11 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
Defaults[.isHideUnknownOn] = hideUnknownSwitch.isOn Defaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
} }
func rememberPassphraseSwitchAction(_ sender: Any?) {
Defaults[.isRememberPassphraseOn] = rememberPassphraseSwitch.isOn
if rememberPassphraseSwitch.isOn == false {
PasswordStore.shared.pgpKeyPassphrase = nil
}
}
} }

View file

@ -21,11 +21,25 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
pgpPassphrase = PasswordStore.shared.pgpKeyPassphrase pgpPassphrase = PasswordStore.shared.pgpKeyPassphrase
} }
private func createSavePassphraseAlert() -> UIAlertController {
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert)
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
Defaults[.isRememberPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
savePassphraseAlert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.destructive) {_ in
Defaults[.isRememberPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
return savePassphraseAlert
}
@IBAction func save(_ sender: Any) { @IBAction func save(_ sender: Any) {
let alert = UIAlertController(title: "Phassphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) 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 alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text self.pgpPassphrase = alert.textFields?.first?.text
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) let savePassphraseAlert = self.createSavePassphraseAlert()
self.present(savePassphraseAlert, animated: true, completion: nil)
})) }))
alert.addTextField(configurationHandler: {(textField: UITextField!) in alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase textField.text = self.pgpPassphrase

View file

@ -51,7 +51,7 @@ class PGPKeySettingTableViewController: UITableViewController {
} }
@IBAction func save(_ sender: Any) { @IBAction func save(_ sender: Any) {
let alert = UIAlertController(title: "Phassphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) 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 alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text self.pgpPassphrase = alert.textFields?.first?.text
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)

View file

@ -17,6 +17,26 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
var passwordCategoryText = "" var passwordCategoryText = ""
var password: Password? var password: Password?
var passwordImage: UIImage? var passwordImage: UIImage?
let indicatorLable: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 21))
label.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.382 + 22)
label.backgroundColor = UIColor.clear
label.textColor = UIColor.gray
label.text = "decrypting password"
label.textAlignment = .center
label.font = UIFont.preferredFont(forTextStyle: .footnote)
return label
}()
let indicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.382)
return indicator
}()
let editUIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
struct TableCell { struct TableCell {
var title: String var title: String
@ -54,19 +74,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
tableView.contentInset = UIEdgeInsetsMake(-36, 0, 0, 0); tableView.contentInset = UIEdgeInsetsMake(-36, 0, 0, 0);
tableView.rowHeight = UITableViewAutomaticDimension tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 52 tableView.estimatedRowHeight = 52
let indicatorLable = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 21))
indicatorLable.center = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height * 0.382 + 22)
indicatorLable.backgroundColor = UIColor.clear
indicatorLable.textColor = UIColor.gray
indicatorLable.text = "decrypting password"
indicatorLable.textAlignment = .center
indicatorLable.font = UIFont.preferredFont(forTextStyle: .footnote)
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.center = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height * 0.382)
indicator.startAnimating() indicator.startAnimating()
tableView.addSubview(indicator) tableView.addSubview(indicator)
tableView.addSubview(indicatorLable) tableView.addSubview(indicatorLable)
let editUIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
editUIBarButtonItem.isEnabled = false editUIBarButtonItem.isEnabled = false
navigationItem.rightBarButtonItem = editUIBarButtonItem navigationItem.rightBarButtonItem = editUIBarButtonItem
@ -75,10 +87,33 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
passwordImage = image passwordImage = image
} }
var passphrase = ""
if Defaults[.isRememberPassphraseOn] && PasswordStore.shared.pgpKeyPassphrase != nil {
passphrase = PasswordStore.shared.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)
}
}
func decryptThenShowPassword(passphrase: String) {
if Defaults[.isRememberPassphraseOn] {
PasswordStore.shared.pgpKeyPassphrase = passphrase
}
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
do { do {
self.password = try self.passwordEntity!.decrypt()! self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)!
} catch { } catch {
DispatchQueue.main.async { DispatchQueue.main.async {
let alert = UIAlertController(title: "Cannot Show Password", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.alert) let alert = UIAlertController(title: "Cannot Show Password", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
@ -89,19 +124,23 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
return return
} }
let password = self.password! let password = self.password!
self.setTableData()
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData() self?.showPassword(password: password)
indicator.stopAnimating() }
indicatorLable.isHidden = true }
editUIBarButtonItem.isEnabled = true }
if let url = password.getURL() {
if self?.passwordEntity?.image == nil{ func showPassword(password: Password) {
self?.updatePasswordImage(url: url) setTableData()
} self.tableView.reloadData()
} indicator.stopAnimating()
indicatorLable.isHidden = true
editUIBarButtonItem.isEnabled = true
if let url = password.getURL() {
if self.passwordEntity?.image == nil{
self.updatePasswordImage(url: url)
} }
} }
} }

View file

@ -190,13 +190,33 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
password = passwordEntities![index] password = passwordEntities![index]
} }
UIImpactFeedbackGenerator(style: .medium).impactOccurred() UIImpactFeedbackGenerator(style: .medium).impactOccurred()
var passphrase = ""
if Defaults[.isRememberPassphraseOn] && PasswordStore.shared.pgpKeyPassphrase != nil {
passphrase = PasswordStore.shared.pgpKeyPassphrase!
self.decryptThenCopyPassword(passwordEntity: password, 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.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = ""
textField.isSecureTextEntry = true
})
self.present(alert, animated: true, completion: nil)
}
}
func decryptThenCopyPassword(passwordEntity: PasswordEntity, passphrase: String) {
SVProgressHUD.setDefaultMaskType(.black) SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.dark) SVProgressHUD.setDefaultStyle(.dark)
SVProgressHUD.show(withStatus: "Decrypting") SVProgressHUD.show(withStatus: "Decrypting")
DispatchQueue.global(qos: .userInteractive).async { DispatchQueue.global(qos: .userInteractive).async {
var decryptedPassword: Password? var decryptedPassword: Password?
do { do {
decryptedPassword = try password.decrypt()! decryptedPassword = try passwordEntity.decrypt(passphrase: passphrase)!
DispatchQueue.main.async { DispatchQueue.main.async {
Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) Utils.copyToPasteboard(textToCopy: decryptedPassword?.password)
SVProgressHUD.showSuccess(withStatus: "Password Copied") SVProgressHUD.showSuccess(withStatus: "Password Copied")

View file

@ -59,7 +59,9 @@ class SettingsTableViewController: UITableViewController {
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController { } else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
Defaults[.pgpKeySource] = "armor" Defaults[.pgpKeySource] = "armor"
PasswordStore.shared.pgpKeyPassphrase = controller.pgpPassphrase PasswordStore.shared.pgpKeyPassphrase = controller.pgpPassphrase
Utils.addPasswrodToKeychain(name: "pgpKeyPassphrase", password: controller.pgpPassphrase!) if Defaults[.isRememberPassphraseOn] {
Utils.addPasswrodToKeychain(name: "pgpKeyPassphrase", password: controller.pgpPassphrase!)
}
Defaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text! Defaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
Defaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text! Defaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!

View file

@ -33,6 +33,7 @@ extension DefaultsKeys {
static let passcodeKey = DefaultsKey<String?>("passcodeKey") static let passcodeKey = DefaultsKey<String?>("passcodeKey")
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn") static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
static let isRememberPassphraseOn = DefaultsKey<Bool>("isRememberPassphraseOn")
static let passwordGenerationMethod = DefaultsKey<String>("passwordGenerationMethod") static let passwordGenerationMethod = DefaultsKey<String>("passwordGenerationMethod")

View file

@ -93,7 +93,7 @@ class Utils {
return nil return nil
} }
static func addPasswrodToKeychain(name: String, password: String) { static func addPasswrodToKeychain(name: String, password: String?) {
let keychain = Keychain(service: "me.mssun.passforios") let keychain = Keychain(service: "me.mssun.passforios")
keychain[name] = password keychain[name] = password
} }

View file

@ -10,11 +10,11 @@ import Foundation
import SwiftyUserDefaults import SwiftyUserDefaults
extension PasswordEntity { extension PasswordEntity {
func decrypt() throws -> Password? { func decrypt(passphrase: String) throws -> Password? {
var password: Password? var password: Password?
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(rawPath!)") let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(rawPath!)")
let encryptedData = try Data(contentsOf: encryptedDataPath) let encryptedData = try Data(contentsOf: encryptedDataPath)
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: PasswordStore.shared.pgpKeyPassphrase!) let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
let plainText = String(data: decryptedData, encoding: .ascii) ?? "" let plainText = String(data: decryptedData, encoding: .ascii) ?? ""
password = Password(name: name!, plainText: plainText) password = Password(name: name!, plainText: plainText)
return password return password

View file

@ -103,7 +103,7 @@ class PasswordStore {
var pgpKeyPassphrase: String? { var pgpKeyPassphrase: String? {
set { set {
Utils.addPasswrodToKeychain(name: "pgpKeyPassphrase", password: newValue!) Utils.addPasswrodToKeychain(name: "pgpKeyPassphrase", password: newValue)
} }
get { get {
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase") return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
@ -111,7 +111,7 @@ class PasswordStore {
} }
var gitRepositoryPassword: String? { var gitRepositoryPassword: String? {
set { set {
Utils.addPasswrodToKeychain(name: "gitRepositoryPassword", password: newValue!) Utils.addPasswrodToKeychain(name: "gitRepositoryPassword", password: newValue)
} }
get { get {
return Utils.getPasswordFromKeychain(name: "gitRepositoryPassword") return Utils.getPasswordFromKeychain(name: "gitRepositoryPassword")