diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index c997143..fdf986e 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ DCC408A41E2FCC9E00F29B0E /* PasswordStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC408A31E2FCC9E00F29B0E /* PasswordStore.swift */; }; DCC408C71E307DBB00F29B0E /* SVProgressHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */; }; DCC441521E8F6C06008A90C4 /* RawPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */; }; + DCC441541E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */; }; DCDDEAB01E4639F300F68193 /* LabelTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */; }; DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */; }; DCE6C2671E71261C003038C6 /* PasswordWithFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE6C2651E71261C003038C6 /* PasswordWithFolderTableViewCell.swift */; }; @@ -134,6 +135,7 @@ DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = ""; }; DCC408C91E30BA1300F29B0E /* pass.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = pass.xcdatamodel; sourceTree = ""; }; DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawPasswordViewController.swift; sourceTree = ""; }; + DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitSSHKeyArmorSettingTableViewController.swift; sourceTree = ""; }; DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LabelTableViewCell.xib; sourceTree = ""; }; DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordDetailTitleTableViewCell.swift; sourceTree = ""; }; DCE6C2651E71261C003038C6 /* PasswordWithFolderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordWithFolderTableViewCell.swift; sourceTree = ""; }; @@ -216,6 +218,7 @@ DCAAF7441E2FA66800AB94BC /* SettingsTableViewController.swift */, DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */, DC8963BF1E38EEB900828B09 /* SSHKeySettingTableViewController.swift */, + DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -518,6 +521,7 @@ files = ( DCC408A41E2FCC9E00F29B0E /* PasswordStore.swift in Sources */, DC037CBF1E4ED4E100609409 /* TextViewTableViewCell.swift in Sources */, + DCC441541E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift in Sources */, DC8963C01E38EEB900828B09 /* SSHKeySettingTableViewController.swift in Sources */, DC193FFA1E49B4430077E0A3 /* AdvancedSettingsTableViewController.swift in Sources */, DCFB77AB1E503729008DE471 /* ContentTableViewCell.swift in Sources */, diff --git a/pass/Base.lproj/Main.storyboard b/pass/Base.lproj/Main.storyboard index fea1560..f91974e 100644 --- a/pass/Base.lproj/Main.storyboard +++ b/pass/Base.lproj/Main.storyboard @@ -477,10 +477,11 @@ - + - + + @@ -752,7 +753,7 @@ - Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. @@ -892,36 +893,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - @@ -931,14 +902,13 @@ - - + @@ -1371,7 +1341,7 @@ pfZ36xQbOAQYKKf6ZTT5R/Y= - + @@ -1409,6 +1379,200 @@ pfZ36xQbOAQYKKf6ZTT5R/Y= + + + + + + + + + + + + + + + + + + + + + + + + ASCII-armored key format similar to uuencoded documents rather than binary format. + + + + + + + + + + $ cat ~/.ssh/id_rsa.pub + $ cat ~/.ssh/id_rsa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFiiqFoBCACzvPOW+J1RlQOI9oB7Iwuk92rzpkKGC8tiwo4otHMc3rxmzYlG +gEdHmSi6Ix0+hYZ2vSWJamPQl9WUiYSBQmkVwoqiwXNWcqRePOylcqsj58owIxZt +23nXUIG78t+5BatoWyl9/qXsMUMSink7RAlOnjCyvoSJw9yzUVeWn723rvAr7ypV +yqjce4gOGIfGT8PpGXThDQjvg4gM+wxUD3+oNhW50Qb+crMsefxwEoGXbCA4+6ZO +nJGy2MhRr4FkXwNAAcvTvTmYXQfH8Q8xCtgl4PyxQA4OAeoEfxl5FOuOGIbNKgMs +133n2oue5dy3YERN2t9/Di8Iov4Bsc+nj6+vABEBAAG0E1Bhc3MgZm9yIGlPUyAo +RGVtbymJATcEEwEKACEFAliiqFoCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AA +CgkQl/oT92AAdt0TqAgAlTsGLTe9+qY9pfyBVpDk+z1SC9kaOYsvoj7hSWVOXxwP +yn4jxjHUr1C9fyaZqP9K4yIdl9jsGBkpaR6lhnUm9x5RO5gEAAAaLhx3ZgE7/XQg +y7WHibxsnLaTEMVnNh8BJzQveYSqO8frBkQofj0K9LNiqWHhcllKKNkVHqyeMRII +KYPOXvDnsnKqywapu5VyyErMnPqq6h7i3Z7+xDfVk8iUew9i0LR/QEmJO7iTH98C +DczQo+4VnflBIHiNcMaXQqC2Li5rS+O3a9hq5V/gJwBtIx1iXvDaRATH4aUANxTZ +POHVLwxBuTwilhnEaAPGkGu+oG1XF+RWOyrGYluWc7kBDQRYoqhaAQgAoh9B+sY1 +jZZgnqP11a9wkjom2DcBJLreYNzgBFYhUcmxtq+ZWyorPFzMJpbIjzMGRiGWFIus +DE5D5LZdAvkINWKsbVN6Y3OI6Tj+/O45SuJ7EusG8Z1bwmjJqHy/Y//Fk0rF6ZFs +g0+p12Xzr5k8sMKUU4I5T4DNrsS+mwnOPItC7NGawkArPD7Mew0ma+Luv7b3g0gA +DuofvxKiN0jq6W9QPUNRY4BKm+Y03CdZNXyMDs/NTguDdfarawTIAIlJOSg5rV7S +74v73dtZkB5LySX1rBomRb5zkCg6/Ygap4RO1aO9gbXyPmc2lUQSBAJCS9d8h0qD +sNGembqhGDwHpQARAQABiQEfBBgBCgAJBQJYoqhaAhsMAAoJEJf6E/dgAHbdKl4H +/A/Q+hOh40M2B6Sh/Rsus+k1VL51+UXVJRyiNiqvofamufF5ULfeq3Pt5HfqpCsF +/A7iRP0MuanKcZbLDjDv6wWDuRvW9Kwn5qjbb6iBPh+wfyReqNMWGksJTyap0SNR ++Mn+sjbxGkUPjzVdRdqtw64R3DLW6SpMNIUVC6paazCViEtHsSauWwP/0+DRA/6j +xBHSAr0UaLsRTHleOSWV6ABrh9skJRWLBcOoi5pxotBs/3BmjqERQf9bwmmyxj2K +cE60Muv6qY4/KCQqPKhyFjJwXhS/e7BwxHEt1SbUb1aC4lH5UrbW9KYLu5lHJRdY +pfZ36xQbOAQYKKf6ZTT5R/Y= +=2pJ8 +-----END PGP PUBLIC KEY BLOCK----- + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFiiqFoBCACzvPOW+J1RlQOI9oB7Iwuk92rzpkKGC8tiwo4otHMc3rxmzYlG +gEdHmSi6Ix0+hYZ2vSWJamPQl9WUiYSBQmkVwoqiwXNWcqRePOylcqsj58owIxZt +23nXUIG78t+5BatoWyl9/qXsMUMSink7RAlOnjCyvoSJw9yzUVeWn723rvAr7ypV +yqjce4gOGIfGT8PpGXThDQjvg4gM+wxUD3+oNhW50Qb+crMsefxwEoGXbCA4+6ZO +nJGy2MhRr4FkXwNAAcvTvTmYXQfH8Q8xCtgl4PyxQA4OAeoEfxl5FOuOGIbNKgMs +133n2oue5dy3YERN2t9/Di8Iov4Bsc+nj6+vABEBAAG0E1Bhc3MgZm9yIGlPUyAo +RGVtbymJATcEEwEKACEFAliiqFoCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AA +CgkQl/oT92AAdt0TqAgAlTsGLTe9+qY9pfyBVpDk+z1SC9kaOYsvoj7hSWVOXxwP +yn4jxjHUr1C9fyaZqP9K4yIdl9jsGBkpaR6lhnUm9x5RO5gEAAAaLhx3ZgE7/XQg +y7WHibxsnLaTEMVnNh8BJzQveYSqO8frBkQofj0K9LNiqWHhcllKKNkVHqyeMRII +KYPOXvDnsnKqywapu5VyyErMnPqq6h7i3Z7+xDfVk8iUew9i0LR/QEmJO7iTH98C +DczQo+4VnflBIHiNcMaXQqC2Li5rS+O3a9hq5V/gJwBtIx1iXvDaRATH4aUANxTZ +POHVLwxBuTwilhnEaAPGkGu+oG1XF+RWOyrGYluWc7kBDQRYoqhaAQgAoh9B+sY1 +jZZgnqP11a9wkjom2DcBJLreYNzgBFYhUcmxtq+ZWyorPFzMJpbIjzMGRiGWFIus +DE5D5LZdAvkINWKsbVN6Y3OI6Tj+/O45SuJ7EusG8Z1bwmjJqHy/Y//Fk0rF6ZFs +g0+p12Xzr5k8sMKUU4I5T4DNrsS+mwnOPItC7NGawkArPD7Mew0ma+Luv7b3g0gA +DuofvxKiN0jq6W9QPUNRY4BKm+Y03CdZNXyMDs/NTguDdfarawTIAIlJOSg5rV7S +74v73dtZkB5LySX1rBomRb5zkCg6/Ygap4RO1aO9gbXyPmc2lUQSBAJCS9d8h0qD +sNGembqhGDwHpQARAQABiQEfBBgBCgAJBQJYoqhaAhsMAAoJEJf6E/dgAHbdKl4H +/A/Q+hOh40M2B6Sh/Rsus+k1VL51+UXVJRyiNiqvofamufF5ULfeq3Pt5HfqpCsF +/A7iRP0MuanKcZbLDjDv6wWDuRvW9Kwn5qjbb6iBPh+wfyReqNMWGksJTyap0SNR ++Mn+sjbxGkUPjzVdRdqtw64R3DLW6SpMNIUVC6paazCViEtHsSauWwP/0+DRA/6j +xBHSAr0UaLsRTHleOSWV6ABrh9skJRWLBcOoi5pxotBs/3BmjqERQf9bwmmyxj2K +cE60Muv6qY4/KCQqPKhyFjJwXhS/e7BwxHEt1SbUb1aC4lH5UrbW9KYLu5lHJRdY +pfZ36xQbOAQYKKf6ZTT5R/Y= +=2pJ8 +-----END PGP PUBLIC KEY BLOCK----- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pass/Controllers/GitSSHKeyArmorSettingTableViewController.swift b/pass/Controllers/GitSSHKeyArmorSettingTableViewController.swift new file mode 100644 index 0000000..cb505c0 --- /dev/null +++ b/pass/Controllers/GitSSHKeyArmorSettingTableViewController.swift @@ -0,0 +1,72 @@ +// +// GitSSHKeyArmorSettingTableViewController.swift +// pass +// +// Created by Mingshen Sun on 2/4/2017. +// Copyright © 2017 Bob Sun. All rights reserved. +// + +import UIKit +import SwiftyUserDefaults + +class GitSSHKeyArmorSettingTableViewController: UITableViewController { + @IBOutlet weak var armorPublicKeyTextView: UITextView! + @IBOutlet weak var armorPrivateKeyTextView: UITextView! + var gitSSHPrivateKeyPassphrase: String? + let passwordStore = PasswordStore.shared + var doneBarButtonItem: UIBarButtonItem? + + override func viewDidLoad() { + super.viewDidLoad() + armorPublicKeyTextView.text = Defaults[.gitSSHPublicKeyArmor] + armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor] + gitSSHPrivateKeyPassphrase = passwordStore.gitSSHPrivateKeyPassphrase + + doneBarButtonItem = UIBarButtonItem(title: "Done", + style: UIBarButtonItemStyle.done, + target: self, + action: #selector(doneButtonTapped(_:))) + navigationItem.rightBarButtonItem = doneBarButtonItem + navigationItem.title = "SSH Key" + } + + private func createSavePassphraseAlert() -> UIAlertController { + let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later sync?", preferredStyle: UIAlertControllerStyle.alert) + savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in + Defaults[.isRememberPassphraseOn] = false + Defaults[.gitSSHKeySource] = "armor" + self.navigationController!.popViewController(animated: true) + }) + savePassphraseAlert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.destructive) {_ in + Defaults[.isRememberPassphraseOn] = true + self.passwordStore.gitSSHPrivateKeyPassphrase = self.gitSSHPrivateKeyPassphrase + Defaults[.gitSSHKeySource] = "armor" + self.navigationController!.popViewController(animated: true) + }) + return savePassphraseAlert + } + + func doneButtonTapped(_ sender: Any) { + Defaults[.gitSSHPublicKeyArmor] = armorPublicKeyTextView.text + Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text + do { + try passwordStore.initGitSSHKey(with: armorPublicKeyTextView.text, .public) + try passwordStore.initGitSSHKey(with: armorPrivateKeyTextView.text, .secret) + } catch { + Utils.alert(title: "Cannot Save", message: "Cannot Save SSH Key", controller: self, completion: nil) + } + let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your SSH secret key.", preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in + self.gitSSHPrivateKeyPassphrase = alert.textFields?.first?.text + let savePassphraseAlert = self.createSavePassphraseAlert() + self.present(savePassphraseAlert, animated: true, completion: nil) + })) + alert.addTextField(configurationHandler: {(textField: UITextField!) in + textField.text = self.gitSSHPrivateKeyPassphrase + textField.isSecureTextEntry = true + }) + self.present(alert, animated: true, completion: nil) + } + + +} diff --git a/pass/Controllers/GitServerSettingTableViewController.swift b/pass/Controllers/GitServerSettingTableViewController.swift index 3f940d5..00eb3a9 100644 --- a/pass/Controllers/GitServerSettingTableViewController.swift +++ b/pass/Controllers/GitServerSettingTableViewController.swift @@ -8,17 +8,18 @@ import UIKit import SwiftyUserDefaults +import SVProgressHUD class GitServerSettingTableViewController: UITableViewController { - @IBOutlet weak var gitRepositoryURLTextField: UITextField! + @IBOutlet weak var gitURLTextField: UITextField! @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var authSSHKeyCell: UITableViewCell! @IBOutlet weak var authPasswordCell: UITableViewCell! let passwordStore = PasswordStore.shared var password: String? - var authenticationMethod = Defaults[.gitRepositoryAuthenticationMethod] + var authenticationMethod = Defaults[.gitAuthenticationMethod] private func checkAuthenticationMethod(method: String) { let passwordCheckView = authPasswordCell.viewWithTag(1001)! @@ -38,17 +39,17 @@ class GitServerSettingTableViewController: UITableViewController { } override func viewDidLoad() { super.viewDidLoad() - if let url = Defaults[.gitRepositoryURL] { - gitRepositoryURLTextField.text = url.absoluteString + if let url = Defaults[.gitURL] { + gitURLTextField.text = url.absoluteString } - usernameTextField.text = Defaults[.gitRepositoryUsername] - password = passwordStore.gitRepositoryPassword - authenticationMethod = Defaults[.gitRepositoryAuthenticationMethod] + usernameTextField.text = Defaults[.gitUsername] + password = passwordStore.gitPassword + authenticationMethod = Defaults[.gitAuthenticationMethod] // Grey out ssh option if ssh_key and ssh_key.pub are not present let sshLabel = authSSHKeyCell.subviews[0].subviews[0] as! UILabel - sshLabel.isEnabled = sshKeyExists() + sshLabel.isEnabled = gitSSHKeyExists() if authenticationMethod == nil || !sshLabel.isEnabled { authenticationMethod = "Password" @@ -61,7 +62,7 @@ class GitServerSettingTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { let cell = tableView.cellForRow(at: indexPath) if cell == authSSHKeyCell { - performSegue(withIdentifier: "showSSHKeySettingSegue", sender: self) + showSSHKeyActionSheet() } } @@ -77,7 +78,7 @@ class GitServerSettingTableViewController: UITableViewController { override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { if identifier == "saveGitServerSettingSegue" { - guard let _ = URL(string: gitRepositoryURLTextField.text!) else { + guard let _ = URL(string: gitURLTextField.text!) else { Utils.alert(title: "Cannot Save", message: "Git Server is not set.", controller: self, completion: nil) return false } @@ -89,18 +90,13 @@ class GitServerSettingTableViewController: UITableViewController { return true } - func sshKeyExists() -> Bool { - return FileManager.default.fileExists(atPath: Globals.sshPublicKeyURL.path) && - FileManager.default.fileExists(atPath: Globals.sshPrivateKeyURL.path) - } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let cell = tableView.cellForRow(at: indexPath) if cell == authPasswordCell { authenticationMethod = "Password" } else if cell == authSSHKeyCell { - if !sshKeyExists() { + if !gitSSHKeyExists() { Utils.alert(title: "Cannot Select SSH Key", message: "Please setup SSH key first.", controller: self, completion: nil) authenticationMethod = "Password" } else { @@ -131,4 +127,74 @@ class GitServerSettingTableViewController: UITableViewController { } } } + + private func gitSSHKeyExists() -> Bool { + return FileManager.default.fileExists(atPath: Globals.gitSSHPublicKeyPath) && + FileManager.default.fileExists(atPath: Globals.gitSSHPrivateKeyPath) + } + + func showSSHKeyActionSheet() { + let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + var urlActionTitle = "Download from URL" + var armorActionTitle = "ASCII-Armor Encrypted Key" + var fileActionTitle = "Use Uploaded Keys" + + if Defaults[.gitSSHKeySource] == "url" { + urlActionTitle = "✓ \(urlActionTitle)" + } else if Defaults[.gitSSHKeySource] == "armor" { + armorActionTitle = "✓ \(armorActionTitle)" + } else if Defaults[.gitSSHKeySource] == "file" { + fileActionTitle = "✓ \(fileActionTitle)" + } + let urlAction = UIAlertAction(title: urlActionTitle, style: .default) { _ in + self.performSegue(withIdentifier: "setGitSSHKeyByURLSegue", sender: self) + } + let armorAction = UIAlertAction(title: armorActionTitle, style: .default) { _ in + self.performSegue(withIdentifier: "setGitSSHKeyByArmorSegue", sender: self) + } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + optionMenu.addAction(urlAction) + optionMenu.addAction(armorAction) + + if (gitSSHKeyExists()) { + let fileAction = UIAlertAction(title: fileActionTitle, style: .default) { _ in + let alert = UIAlertController( + title: "SSH Key Passphrase", + message: "Please fill in the passphrase for your Git Repository SSH key.", + preferredStyle: UIAlertControllerStyle.alert + ) + + alert.addAction( + UIAlertAction( + title: "OK", + style: UIAlertActionStyle.default, + handler: {_ in + self.passwordStore.gitSSHPrivateKeyPassphrase = alert.textFields!.first!.text! + } + ) + ) + + alert.addTextField( + configurationHandler: {(textField: UITextField!) in + textField.text = self.passwordStore.gitSSHPrivateKeyPassphrase + textField.isSecureTextEntry = true + } + ) + } + Defaults[.gitSSHKeySource] = "file" + optionMenu.addAction(fileAction) + } + + if Defaults[.gitSSHKeySource] != nil { + let deleteAction = UIAlertAction(title: "Remove Git SSH Keys", style: .destructive) { _ in + Utils.removeGitSSHKeys() + Defaults[.gitSSHKeySource] = nil + } + optionMenu.addAction(deleteAction) + } + optionMenu.addAction(cancelAction) + optionMenu.popoverPresentationController?.sourceView = authSSHKeyCell + optionMenu.popoverPresentationController?.sourceRect = authSSHKeyCell.bounds + self.present(optionMenu, animated: true, completion: nil) + } } diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index cef43d0..2d221c7 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -143,7 +143,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV } DispatchQueue.main.async { self.reloadTableView(parent: nil) - Defaults[.gitRepositoryPasswordAttempts] = 0 + Defaults[.gitPasswordAttempts] = 0 SVProgressHUD.showSuccess(withStatus: "Done") SVProgressHUD.dismiss(withDelay: 1) } diff --git a/pass/Controllers/SSHKeySettingTableViewController.swift b/pass/Controllers/SSHKeySettingTableViewController.swift index 517ea23..eec6800 100644 --- a/pass/Controllers/SSHKeySettingTableViewController.swift +++ b/pass/Controllers/SSHKeySettingTableViewController.swift @@ -12,15 +12,14 @@ import SVProgressHUD class SSHKeySettingTableViewController: UITableViewController { - @IBOutlet weak var passphraseTextField: UITextField! @IBOutlet weak var privateKeyURLTextField: UITextField! @IBOutlet weak var publicKeyURLTextField: UITextField! + let passwordStore = PasswordStore.shared override func viewDidLoad() { super.viewDidLoad() - passphraseTextField.text = Utils.getPasswordFromKeychain(name: "gitRepositorySSHPrivateKeyPassphrase") ?? "" - privateKeyURLTextField.text = Defaults[.gitRepositorySSHPrivateKeyURL]?.absoluteString - publicKeyURLTextField.text = Defaults[.gitRepositorySSHPublicKeyURL]?.absoluteString + privateKeyURLTextField.text = Defaults[.gitSSHPrivateKeyURL]?.absoluteString + publicKeyURLTextField.text = Defaults[.gitSSHPublicKeyURL]?.absoluteString var doneBarButtonItem: UIBarButtonItem? doneBarButtonItem = UIBarButtonItem(title: "Done", @@ -41,18 +40,42 @@ class SSHKeySettingTableViewController: UITableViewController { return } - Defaults[.gitRepositorySSHPublicKeyURL] = publicKeyURL - Defaults[.gitRepositorySSHPrivateKeyURL] = privateKeyURL - Utils.addPasswordToKeychain(name: "gitRepositorySSHPrivateKeyPassphrase", password: passphraseTextField.text!) + Defaults[.gitSSHPublicKeyURL] = publicKeyURL + Defaults[.gitSSHPrivateKeyURL] = privateKeyURL do { - try Data(contentsOf: publicKeyURL).write(to: Globals.sshPublicKeyURL, options: .atomic) - try Data(contentsOf: privateKeyURL).write(to: Globals.sshPrivateKeyURL, options: .atomic) + try Data(contentsOf: publicKeyURL).write(to: URL(fileURLWithPath: Globals.gitSSHPublicKeyPath), options: .atomic) + try Data(contentsOf: privateKeyURL).write(to: URL(fileURLWithPath: Globals.gitSSHPrivateKeyPath), options: .atomic) } catch { Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) } - - navigationController!.popViewController(animated: true) + Defaults[.gitSSHKeySource] = "url" + let alert = UIAlertController( + title: "PGP Passphrase", + message: "Please fill in the passphrase for your Git Repository SSH key.", + preferredStyle: UIAlertControllerStyle.alert + ) + + alert.addAction( + UIAlertAction( + title: "OK", + style: UIAlertActionStyle.default, + handler: {_ in + Utils.addPasswordToKeychain( + name: "gitSSHPrivateKeyPassphrase", + password: alert.textFields!.first!.text! + ) + self.navigationController!.popViewController(animated: true) + } + ) + ) + + alert.addTextField( + configurationHandler: {(textField: UITextField!) in + textField.text = self.passwordStore.gitSSHPrivateKeyPassphrase + textField.isSecureTextEntry = true + }) + self.present(alert, animated: true, completion: nil) } } diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift index 87f7321..764b688 100644 --- a/pass/Controllers/SettingsTableViewController.swift +++ b/pass/Controllers/SettingsTableViewController.swift @@ -95,16 +95,16 @@ class SettingsTableViewController: UITableViewController { @IBAction func saveGitServerSetting(segue: UIStoryboardSegue) { if let controller = segue.source as? GitServerSettingTableViewController { - let gitRepostiroyURL = controller.gitRepositoryURLTextField.text! + let gitRepostiroyURL = controller.gitURLTextField.text! let username = controller.usernameTextField.text! let password = controller.password let auth = controller.authenticationMethod - if Defaults[.gitRepositoryURL] == nil || - Defaults[.gitRepositoryURL]!.absoluteString != gitRepostiroyURL || - auth != Defaults[.gitRepositoryAuthenticationMethod] || - username != Defaults[.gitRepositoryUsername] || - password != self.passwordStore.gitRepositoryPassword || + if Defaults[.gitURL] == nil || + Defaults[.gitURL]!.absoluteString != gitRepostiroyURL || + auth != Defaults[.gitAuthenticationMethod] || + username != Defaults[.gitUsername] || + password != self.passwordStore.gitPassword || self.passwordStore.repositoryExisted() == false { SVProgressHUD.setDefaultMaskType(.black) @@ -117,9 +117,9 @@ class SettingsTableViewController: UITableViewController { gitCredential = GitCredential( credential: GitCredential.Credential.ssh( userName: username, - password: Utils.getPasswordFromKeychain(name: "gitRepositorySSHPrivateKeyPassphrase") ?? "", - publicKeyFile: Globals.sshPublicKeyURL, - privateKeyFile: Globals.sshPrivateKeyURL, + password: Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase") ?? "", + publicKeyFile: Globals.gitSSHPublicKeyURL, + privateKeyFile: Globals.gitSSHPrivateKeyURL, passwordNotSetCallback: self.requestSshKeyPassword ) ) @@ -140,11 +140,11 @@ class SettingsTableViewController: UITableViewController { } }) DispatchQueue.main.async { - Defaults[.gitRepositoryURL] = URL(string: gitRepostiroyURL) - Defaults[.gitRepositoryUsername] = username - Defaults[.gitRepositoryAuthenticationMethod] = auth - Defaults[.gitRepositoryPasswordAttempts] = 0 - self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitRepositoryURL]?.host + Defaults[.gitURL] = URL(string: gitRepostiroyURL) + Defaults[.gitUsername] = username + Defaults[.gitAuthenticationMethod] = auth + Defaults[.gitPasswordAttempts] = 0 + self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]?.host SVProgressHUD.showSuccess(withStatus: "Done") SVProgressHUD.dismiss(withDelay: 1) } @@ -162,7 +162,7 @@ class SettingsTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: .passwordStoreErased, object: nil) - self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitRepositoryURL]?.host + self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]?.host touchIDTableViewCell.accessoryView = touchIDSwitch setPGPKeyTableViewCellDetailText() setPasswordRepositoryTableViewCellDetailText() @@ -189,10 +189,10 @@ class SettingsTableViewController: UITableViewController { } private func setPasswordRepositoryTableViewCellDetailText() { - if Defaults[.gitRepositoryURL] == nil { + if Defaults[.gitURL] == nil { passwordRepositoryTableViewCell.detailTextLabel?.text = "Not Set" } else { - passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitRepositoryURL]!.host + passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]!.host } } @@ -218,7 +218,7 @@ class SettingsTableViewController: UITableViewController { })) alert.addTextField(configurationHandler: {(textField: UITextField!) in - textField.text = self.passwordStore.gitRepositoryPassword + textField.text = self.passwordStore.gitPassword textField.isSecureTextEntry = true }) diff --git a/pass/Helpers/DefaultsKeys.swift b/pass/Helpers/DefaultsKeys.swift index f047031..1dae85a 100644 --- a/pass/Helpers/DefaultsKeys.swift +++ b/pass/Helpers/DefaultsKeys.swift @@ -17,12 +17,17 @@ extension DefaultsKeys { static let pgpPublicKeyArmor = DefaultsKey("pgpPublicKeyArmor") static let pgpPrivateKeyArmor = DefaultsKey("pgpPrivateKeyArmor") - static let gitRepositoryURL = DefaultsKey("gitRepositoryURL") - static let gitRepositoryAuthenticationMethod = DefaultsKey("gitRepositoryAuthenticationMethod") - static let gitRepositoryUsername = DefaultsKey("gitRepositoryUsername") - static let gitRepositoryPasswordAttempts = DefaultsKey("gitRepositoryPasswordAttempts") - static let gitRepositorySSHPublicKeyURL = DefaultsKey("gitRepositorySSHPublicKeyURL") - static let gitRepositorySSHPrivateKeyURL = DefaultsKey("gitRepositorySSHPrivateKeyURL") + static let gitURL = DefaultsKey("gitURL") + static let gitAuthenticationMethod = DefaultsKey("gitAuthenticationMethod") + static let gitUsername = DefaultsKey("gitUsername") + static let gitPasswordAttempts = DefaultsKey("gitPasswordAttempts") + static let gitSSHPublicKeyURL = DefaultsKey("gitSSHPublicKeyURL") + static let gitSSHPrivateKeyURL = DefaultsKey("gitSSHPrivateKeyURL") + static let gitSSHKeySource = DefaultsKey("gitSSHKeySource") + + static let gitSSHPublicKeyArmor = DefaultsKey("gitSSHPublicKeyArmor") + static let gitSSHPrivateKeyArmor = DefaultsKey("gitSSHPrivateKeyArmor") + static let lastSyncedTime = DefaultsKey("lastSyncedTime") diff --git a/pass/Helpers/Globals.swift b/pass/Helpers/Globals.swift index 069ff6b..13a3e59 100644 --- a/pass/Helpers/Globals.swift +++ b/pass/Helpers/Globals.swift @@ -15,8 +15,10 @@ class Globals { static let pgpPublicKeyPath = "\(documentPath)/gpg_key.pub" static let pgpPrivateKeyPath = "\(documentPath)/gpg_key" - static let sshPublicKeyURL = URL(fileURLWithPath: "\(documentPath)/ssh_key.pub") - static let sshPrivateKeyURL = URL(fileURLWithPath: "\(documentPath)/ssh_key") + static let gitSSHPublicKeyPath = "\(documentPath)/ssh_key.pub" + static let gitSSHPrivateKeyPath = "\(documentPath)/ssh_key" + static let gitSSHPublicKeyURL = URL(fileURLWithPath: gitSSHPublicKeyPath) + static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath) static let repositoryPath = "\(libraryPath)/password-store" static var passcodeConfiguration = PasscodeLockConfiguration() diff --git a/pass/Helpers/Utils.swift b/pass/Helpers/Utils.swift index e1c539d..5dcd647 100644 --- a/pass/Helpers/Utils.swift +++ b/pass/Helpers/Utils.swift @@ -83,6 +83,16 @@ class Utils { Utils.removeKeychain(name: ".pgpKeyPassphrase") } + static func removeGitSSHKeys() { + removeFileIfExists(atPath: Globals.gitSSHPublicKeyPath) + removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath) + Defaults.remove(.gitSSHPublicKeyArmor) + Defaults.remove(.gitSSHPrivateKeyArmor) + Defaults.remove(.gitSSHPublicKeyURL) + Defaults.remove(.gitSSHPrivateKeyURL) + Utils.removeKeychain(name: ".gitSSHPrivateKeyPassphrase") + } + static func getPasswordFromKeychain(name: String) -> String? { let keychain = Keychain(service: "me.mssun.passforios") do { diff --git a/pass/Models/PasswordStore.swift b/pass/Models/PasswordStore.swift index 3f846ca..ee782ef 100644 --- a/pass/Models/PasswordStore.swift +++ b/pass/Models/PasswordStore.swift @@ -27,9 +27,9 @@ struct GitCredential { var credential: GTCredential? = nil switch self.credential { case let .http(userName, password): - print(Defaults[.gitRepositoryPasswordAttempts]) + print(Defaults[.gitPasswordAttempts]) var newPassword: String = password - if Defaults[.gitRepositoryPasswordAttempts] != 0 { + if Defaults[.gitPasswordAttempts] != 0 { let sem = DispatchSemaphore(value: 0) DispatchQueue.main.async { SVProgressHUD.dismiss() @@ -40,15 +40,15 @@ struct GitCredential { let alert = UIAlertController(title: "Password", message: "Please fill in the password of your Git account.", preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in newPassword = alert.textFields!.first!.text! - PasswordStore.shared.gitRepositoryPassword = newPassword + PasswordStore.shared.gitPassword = newPassword sem.signal() })) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in - Defaults[.gitRepositoryPasswordAttempts] = -1 + Defaults[.gitPasswordAttempts] = -1 sem.signal() }) alert.addTextField(configurationHandler: {(textField: UITextField!) in - textField.text = PasswordStore.shared.gitRepositoryPassword + textField.text = PasswordStore.shared.gitPassword textField.isSecureTextEntry = true }) topController.present(alert, animated: true, completion: nil) @@ -56,12 +56,12 @@ struct GitCredential { } let _ = sem.wait(timeout: DispatchTime.distantFuture) } - if Defaults[.gitRepositoryPasswordAttempts] == -1 { - Defaults[.gitRepositoryPasswordAttempts] = 0 + if Defaults[.gitPasswordAttempts] == -1 { + Defaults[.gitPasswordAttempts] = 0 return nil } - Defaults[.gitRepositoryPasswordAttempts] += 1 - PasswordStore.shared.gitRepositoryPassword = newPassword + Defaults[.gitPasswordAttempts] += 1 + PasswordStore.shared.gitPassword = newPassword credential = try? GTCredential(userName: userName, password: newPassword) case let .ssh(userName, password, publicKeyFile, privateKeyFile, passwordNotSetCallback): @@ -76,7 +76,7 @@ struct GitCredential { } // Save password for the future - Utils.addPasswordToKeychain(name: "gitRepositorySSHPrivateKeyPassphrase", password: newPassword!) + Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newPassword!) // nil is expected in case of empty password if newPassword == "" { @@ -112,7 +112,7 @@ class PasswordStore { var gitSignatureForNow: GTSignature { get { - return GTSignature(name: Defaults[.gitRepositoryUsername]!, email: Defaults[.gitRepositoryUsername]!+"@passforios", time: Date())! + return GTSignature(name: Defaults[.gitUsername]!, email: Defaults[.gitUsername]!+"@passforios", time: Date())! } } @@ -126,12 +126,21 @@ class PasswordStore { return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase") } } - var gitRepositoryPassword: String? { + var gitPassword: String? { set { - Utils.addPasswordToKeychain(name: "gitRepositoryPassword", password: newValue) + Utils.addPasswordToKeychain(name: "gitPassword", password: newValue) } get { - return Utils.getPasswordFromKeychain(name: "gitRepositoryPassword") + return Utils.getPasswordFromKeychain(name: "gitPassword") + } + } + + var gitSSHPrivateKeyPassphrase: String? { + set { + Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newValue) + } + get { + return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase") ?? "" } } @@ -164,22 +173,41 @@ class PasswordStore { print(error) } initPGPKeys() - if Defaults[.gitRepositoryAuthenticationMethod] == "Password" { - gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitRepositoryUsername]!, password: Utils.getPasswordFromKeychain(name: "gitRepositoryPassword") ?? "")) - } else if Defaults[.gitRepositoryAuthenticationMethod] == "SSH Key"{ + initGitCredential() + } + + enum SSHKeyType { + case `public`, secret + } + + public func initGitCredential() { + if Defaults[.gitAuthenticationMethod] == "Password" { + gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitUsername]!, password: Utils.getPasswordFromKeychain(name: "gitPassword") ?? "")) + } else if Defaults[.gitAuthenticationMethod] == "SSH Key"{ gitCredential = GitCredential( credential: GitCredential.Credential.ssh( - userName: Defaults[.gitRepositoryUsername]!, - password: Utils.getPasswordFromKeychain(name: "gitRepositorySSHPrivateKeyPassphrase") ?? "", - publicKeyFile: Globals.sshPublicKeyURL, - privateKeyFile: Globals.sshPrivateKeyURL, + userName: Defaults[.gitUsername]!, + password: gitSSHPrivateKeyPassphrase ?? "", + publicKeyFile: Globals.gitSSHPublicKeyURL, + privateKeyFile: Globals.gitSSHPrivateKeyURL, passwordNotSetCallback: nil ) ) } else { gitCredential = nil } + } + + public func initGitSSHKey(with armorKey: String, _ keyType: SSHKeyType) throws { + var keyPath = "" + switch keyType { + case .public: + keyPath = Globals.gitSSHPublicKeyPath + case .secret: + keyPath = Globals.gitSSHPrivateKeyPath + } + try armorKey.write(toFile: keyPath, atomically: true, encoding: .ascii) } public func initPGPKeys() { @@ -654,8 +682,8 @@ class PasswordStore { Utils.removeFileIfExists(atPath: Globals.pgpPublicKeyPath) Utils.removeFileIfExists(atPath: Globals.pgpPrivateKeyPath) - Utils.removeFileIfExists(at: Globals.sshPrivateKeyURL) - Utils.removeFileIfExists(at: Globals.sshPublicKeyURL) + Utils.removeFileIfExists(atPath: Globals.gitSSHPublicKeyPath) + Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath) Utils.removeAllKeychain()