Initial implementation of using YubiKey for decryption (#533)

This commit is contained in:
Mingshen Sun 2022-01-09 21:38:39 -08:00 committed by GitHub
parent 13804b79e6
commit 955e50c3d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 606 additions and 118 deletions

View file

@ -101,7 +101,7 @@ extension PGPKeyArmorImportTableViewController: PGPKeyImporter {
Utils.alert(title: "CannotSave".localize(), message: "SetPublicKey.".localize(), controller: self, completion: nil)
return false
}
guard !armorPrivateKeyTextView.text.isEmpty else {
guard Defaults.isYubiKeyEnabled || !armorPrivateKeyTextView.text.isEmpty else {
Utils.alert(title: "CannotSave".localize(), message: "SetPrivateKey.".localize(), controller: self, completion: nil)
return false
}

View file

@ -8,7 +8,7 @@
import passKit
class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController, AlertPresenting {
@IBOutlet var pgpPublicKeyFile: UITableViewCell!
@IBOutlet var pgpPrivateKeyFile: UITableViewCell!
@ -25,7 +25,7 @@ class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
let picker = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .open)
let picker = UIDocumentPickerViewController(documentTypes: ["public.item"], in: .open)
cell?.isSelected = false
if cell == pgpPublicKeyFile {
currentlyPicking = .public
@ -71,7 +71,7 @@ extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate {
}
} catch {
let message = "FileCannotBeImported.".localize(fileName) | "UnderlyingError".localize(error.localizedDescription)
Utils.alert(title: "CannotImportFile".localize(), message: message, controller: self)
presentFailureAlert(title: "CannotImportFile".localize(), message: message)
}
}
}
@ -81,19 +81,20 @@ extension PGPKeyFileImportTableViewController: PGPKeyImporter {
static let label = "LoadFromFiles".localize()
func isReadyToUse() -> Bool {
validate(key: publicKey) && validate(key: privateKey)
validate(key: publicKey) && (Defaults.isYubiKeyEnabled || validate(key: privateKey))
}
func importKeys() throws {
guard let publicKey = publicKey, let privateKey = privateKey else {
return
if let publicKey = publicKey {
try KeyFileManager.PublicPGP.importKey(from: publicKey)
}
if let privateKey = privateKey {
try KeyFileManager.PrivatePGP.importKey(from: privateKey)
}
try KeyFileManager.PublicPGP.importKey(from: publicKey)
try KeyFileManager.PrivatePGP.importKey(from: privateKey)
}
func doAfterImport() {
Utils.alert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromLocation.".localize(), controller: self)
presentAlert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromLocation.".localize())
}
func saveImportedKeys() {
@ -102,7 +103,7 @@ extension PGPKeyFileImportTableViewController: PGPKeyImporter {
private func validate(key: String?) -> Bool {
guard key != nil else {
Utils.alert(title: "CannotSavePgpKey".localize(), message: "KeyFileNotSet.".localize(), controller: self)
presentFailureAlert(title: "CannotSavePgpKey".localize(), message: "KeyFileNotSet.".localize())
return false
}
return true

View file

@ -9,7 +9,7 @@
import passKit
import UIKit
class PGPKeyURLImportTableViewController: AutoCellHeightUITableViewController {
class PGPKeyURLImportTableViewController: AutoCellHeightUITableViewController, AlertPresenting {
@IBOutlet var pgpPublicKeyURLTextField: UITextField!
@IBOutlet var pgpPrivateKeyURLTextField: UITextField!
@ -24,18 +24,11 @@ class PGPKeyURLImportTableViewController: AutoCellHeightUITableViewController {
@IBAction
private func save(_: Any) {
guard let publicKeyURLText = pgpPublicKeyURLTextField.text,
let publicKeyURL = URL(string: publicKeyURLText),
let privateKeyURLText = pgpPrivateKeyURLTextField.text,
let privateKeyURL = URL(string: privateKeyURLText) else {
Utils.alert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize(), controller: self)
return
pgpPublicKeyURL = validate(pgpKeyURLText: pgpPublicKeyURLTextField.text)
if !Defaults.isYubiKeyEnabled {
pgpPrivateKeyURL = validate(pgpKeyURLText: pgpPrivateKeyURLTextField.text)
}
if privateKeyURL.scheme?.lowercased() == "http" || publicKeyURL.scheme?.lowercased() == "http" {
Utils.alert(title: "HttpNotSecure".localize(), message: "ReallyUseHttp.".localize(), controller: self)
}
pgpPrivateKeyURL = privateKeyURL
pgpPublicKeyURL = publicKeyURL
saveImportedKeys()
}
}
@ -45,35 +38,39 @@ extension PGPKeyURLImportTableViewController: PGPKeyImporter {
static let label = "DownloadFromUrl".localize()
func isReadyToUse() -> Bool {
validate(pgpKeyURL: pgpPublicKeyURLTextField.text ?? "")
&& validate(pgpKeyURL: pgpPrivateKeyURLTextField.text ?? "")
validate(pgpKeyURLText: pgpPublicKeyURLTextField.text) != nil
&& (Defaults.isYubiKeyEnabled || validate(pgpKeyURLText: pgpPrivateKeyURLTextField.text ?? "") != nil)
}
func importKeys() throws {
Defaults.pgpPrivateKeyURL = pgpPrivateKeyURL
Defaults.pgpPublicKeyURL = pgpPublicKeyURL
if let pgpPrivateKeyURL = pgpPrivateKeyURL {
Defaults.pgpPrivateKeyURL = pgpPrivateKeyURL
try KeyFileManager.PrivatePGP.importKey(from: pgpPrivateKeyURL)
}
try KeyFileManager.PublicPGP.importKey(from: Defaults.pgpPublicKeyURL!)
try KeyFileManager.PrivatePGP.importKey(from: Defaults.pgpPrivateKeyURL!)
if let pgpPublicKeyURL = pgpPublicKeyURL {
Defaults.pgpPublicKeyURL = pgpPublicKeyURL
try KeyFileManager.PublicPGP.importKey(from: pgpPublicKeyURL)
}
}
func doAfterImport() {
Utils.alert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromServer.".localize(), controller: self)
presentAlert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromServer.".localize())
}
func saveImportedKeys() {
performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}
private func validate(pgpKeyURL: String) -> Bool {
guard let url = URL(string: pgpKeyURL) else {
Utils.alert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize(), controller: self)
return false
private func validate(pgpKeyURLText: String?) -> URL? {
guard let pgpKeyURL = pgpKeyURLText, let url = URL(string: pgpKeyURL) else {
presentFailureAlert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize())
return nil
}
guard url.scheme == "https" || url.scheme == "http" else {
Utils.alert(title: "CannotSavePgpKey".localize(), message: "UseEitherHttpsOrHttp.".localize(), controller: self)
return false
presentFailureAlert(title: "CannotSavePgpKey".localize(), message: "UseEitherHttpsOrHttp.".localize())
return nil
}
return true
return url
}
}

View file

@ -7,11 +7,13 @@
//
import FavIcon
import Gopenpgp
import passAutoFillExtension
import passKit
import SVProgressHUD
import UIKit
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate, AlertPresenting {
var passwordEntity: PasswordEntity?
private var password: Password?
private var passwordImage: UIImage?
@ -87,7 +89,15 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
decryptThenShowPassword()
}
private func decryptThenShowPassword(keyID: String? = nil) {
private func decryptThenShowPassword() {
if Defaults.isYubiKeyEnabled {
decryptThenShowPasswordYubiKey()
} else {
decryptThenShowPasswordLocalKey()
}
}
private func decryptThenShowPasswordLocalKey(keyID: String? = nil) {
guard let passwordEntity = passwordEntity else {
Utils.alert(title: "CannotShowPassword".localize(), message: "PasswordDoesNotExist".localize(), controller: self, completion: {
self.navigationController!.popViewController(animated: true)
@ -106,7 +116,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
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.decryptThenShowPassword(keyID: action.title)
self.decryptThenShowPasswordLocalKey(keyID: action.title)
}
alert.addAction(selectKey)
@ -119,7 +129,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
alert.addAction(
UIAlertAction(title: "TryAgain".localize(), style: .default) { _ in
self.decryptThenShowPassword()
self.decryptThenShowPasswordLocalKey()
}
)
self.present(alert, animated: true, completion: nil)
@ -509,3 +519,66 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension PasswordDetailTableViewController {
private func requestYubiKeyPIN(completion: @escaping (String) -> Void) {
let alert = UIAlertController(title: "YubiKey PIN", message: "Verify YubiKey OpenPGP PIN.", preferredStyle: .alert)
alert.addAction(
UIAlertAction.cancel { _ in
self.navigationController!.popViewController(animated: true)
}
)
alert.addAction(
UIAlertAction.ok { _ in
let pin = alert.textFields?.first?.text ?? ""
completion(pin)
}
)
alert.addTextField { textField in
textField.isSecureTextEntry = true
}
present(alert, animated: true)
}
private func handleError(error: AppError) {
switch error {
case let .yubiKey(yubiKeyError):
let errorMessage = yubiKeyError.localizedDescription
if #available(iOS 13.0, *) {
YubiKitManager.shared.stopNFCConnection(withErrorMessage: errorMessage)
DispatchQueue.main.async {
self.navigationController?.popViewController(animated: true)
}
} else {
DispatchQueue.main.async {
self.presentFailureAlert(message: errorMessage) { _ in
self.navigationController?.popViewController(animated: true)
}
}
}
default:
DispatchQueue.main.async {
self.presentFailureAlert(message: error.localizedDescription) { _ in
self.navigationController?.popViewController(animated: true)
}
}
}
}
private func handleCancellation(_: Error) {
DispatchQueue.main.async {
self.navigationController?.popViewController(animated: true)
}
}
private func decryptThenShowPasswordYubiKey() {
guard let passwordEntity = passwordEntity else {
handleError(error: AppError.other(message: "PasswordDoesNotExist"))
return
}
Pass.yubiKeyDecrypt(passwordEntity: passwordEntity, requestPIN: requestYubiKeyPIN, errorHandler: handleError, cancellation: handleCancellation) { password in
self.password = password
self.showPassword()
}
}
}

View file

@ -319,7 +319,7 @@ extension PasswordNavigationViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender _: Any?) -> Bool {
if identifier == "showPasswordDetail" {
guard PGPAgent.shared.isPrepared else {
guard Defaults.isYubiKeyEnabled || PGPAgent.shared.isPrepared else {
Utils.alert(title: "CannotShowPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self)
return false
}

View file

@ -87,12 +87,16 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
private func setPGPKeyTableViewCellDetailText() {
var label = "NotSet".localize()
let keyID = (try? PGPAgent.shared.getShortKeyID()) ?? []
if keyID.count == 1 {
label = keyID.first ?? ""
} else if keyID.count > 1 {
label = "Multiple"
}
if Defaults.isYubiKeyEnabled {
label += "+YubiKey"
}
pgpKeyTableViewCell.detailTextLabel?.text = label
}
@ -180,6 +184,13 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
)
}
optionMenu.addAction(
UIAlertAction(title: Defaults.isYubiKeyEnabled ? "✓ YubiKey" : "YubiKey", style: .default) { _ in
Defaults.isYubiKeyEnabled.toggle()
self.setPGPKeyTableViewCellDetailText()
}
)
if Defaults.pgpKeySource != nil {
optionMenu.addAction(
UIAlertAction(title: "RemovePgpKeys".localize(), style: .destructive) { _ in