Separate encryption/decryption logic for different frameworks used

This commit is contained in:
Danny Moesch 2019-09-08 23:00:46 +02:00 committed by Mingshen Sun
parent e2201ffa52
commit 730542d5bb
24 changed files with 428 additions and 414 deletions

View file

@ -63,6 +63,9 @@
30C25DD721F4834D00BB27BB /* UILocalizedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C25DD521F4834D00BB27BB /* UILocalizedLabel.swift */; };
30C25DD821F4834D00BB27BB /* UICodeHighlightingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C25DD621F4834D00BB27BB /* UICodeHighlightingLabel.swift */; };
30CCA90B2325119C0048CA51 /* Data+Mutable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA90A2325119C0048CA51 /* Data+Mutable.swift */; };
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA91523258C380048CA51 /* PgpInterface.swift */; };
30CCA91823258E760048CA51 /* GopenPgp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA91723258E760048CA51 /* GopenPgp.swift */; };
30CCA91A232591320048CA51 /* ObjectivePgp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA919232591320048CA51 /* ObjectivePgp.swift */; };
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */; };
3EA2386CD0E9CE2A702A0B3E /* Pods_pass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE627E8F3DACEDD8FA220081 /* Pods_pass.framework */; };
556EC3D322335C5F00934F9C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30BF5ECA21EA8FB5000E4154 /* Localizable.strings */; };
@ -280,6 +283,9 @@
30C25DD521F4834D00BB27BB /* UILocalizedLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILocalizedLabel.swift; sourceTree = "<group>"; };
30C25DD621F4834D00BB27BB /* UICodeHighlightingLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICodeHighlightingLabel.swift; sourceTree = "<group>"; };
30CCA90A2325119C0048CA51 /* Data+Mutable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Mutable.swift"; sourceTree = "<group>"; };
30CCA91523258C380048CA51 /* PgpInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PgpInterface.swift; sourceTree = "<group>"; };
30CCA91723258E760048CA51 /* GopenPgp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GopenPgp.swift; sourceTree = "<group>"; };
30CCA919232591320048CA51 /* ObjectivePgp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectivePgp.swift; sourceTree = "<group>"; };
30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = "<group>"; };
3B2B2F844061EFA534FE9506 /* Pods_passKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_passKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
@ -469,6 +475,7 @@
isa = PBXGroup;
children = (
30A86F94230F237000F821A4 /* CryptoFrameworkTest.swift */,
A2AA934522DE3A8000D79A00 /* PGPAgentTest.swift */,
);
path = Crypto;
sourceTree = "<group>";
@ -525,7 +532,6 @@
isa = PBXGroup;
children = (
30B0485F209A5141001013CA /* PasswordTest.swift */,
A2AA934522DE3A8000D79A00 /* PGPAgentTest.swift */,
);
path = Models;
sourceTree = "<group>";
@ -540,6 +546,17 @@
path = UserInterface;
sourceTree = "<group>";
};
30CCA90C232584560048CA51 /* Crypto */ = {
isa = PBXGroup;
children = (
30CCA91723258E760048CA51 /* GopenPgp.swift */,
30CCA919232591320048CA51 /* ObjectivePgp.swift */,
A2AA934322DE30DD00D79A00 /* PGPAgent.swift */,
30CCA91523258C380048CA51 /* PgpInterface.swift */,
);
path = Crypto;
sourceTree = "<group>";
};
A2168A801EFD431A005EA873 /* Controllers */ = {
isa = PBXGroup;
children = (
@ -580,6 +597,7 @@
A26075791EEC6F34005DB03E /* passKit */ = {
isa = PBXGroup;
children = (
30CCA90C232584560048CA51 /* Crypto */,
A2C532B9201DD07500DB9F53 /* Controllers */,
30B6AABA21F49095006B352D /* Extensions */,
A2F4E20F1EED7F0A0011986E /* Helpers */,
@ -638,7 +656,6 @@
30697C4021F63CAB0064FCAC /* Password.swift */,
30697C3F21F63CAA0064FCAC /* PasswordEntity.swift */,
30697C4321F63CAB0064FCAC /* PasswordStore.swift */,
A2AA934322DE30DD00D79A00 /* PGPAgent.swift */,
);
path = Models;
sourceTree = "<group>";
@ -1293,10 +1310,12 @@
A2AA934422DE30DD00D79A00 /* PGPAgent.swift in Sources */,
30697C3B21F63C990064FCAC /* String+Localization.swift in Sources */,
302E85612125ECC70031BA64 /* Parser.swift in Sources */,
30CCA91A232591320048CA51 /* ObjectivePgp.swift in Sources */,
30697C4621F63CAB0064FCAC /* GitCredential.swift in Sources */,
30A1D2A621B2D46100E2D1F7 /* OtpType.swift in Sources */,
3032328E22CBD4CD009EBD9C /* CryptographicKeys.swift in Sources */,
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */,
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */,
30697C4721F63CAB0064FCAC /* PasscodeLock.swift in Sources */,
30697C3421F63C8B0064FCAC /* PasscodeLockViewController.swift in Sources */,
30697C2C21F63C5A0064FCAC /* FileManagerExtension.swift in Sources */,
@ -1304,6 +1323,7 @@
30697C3D21F63C990064FCAC /* UIViewExtension.swift in Sources */,
30697C3A21F63C990064FCAC /* UIViewControllerExtension.swift in Sources */,
30697C2E21F63C5A0064FCAC /* Utils.swift in Sources */,
30CCA91823258E760048CA51 /* GopenPgp.swift in Sources */,
30697C4521F63CAB0064FCAC /* Password.swift in Sources */,
30697C4421F63CAB0064FCAC /* PasswordEntity.swift in Sources */,
30BAC8CD22E3BB9700438475 /* KeyStore.swift in Sources */,

View file

@ -31,7 +31,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveAddPasswordSegue" {
// check PGP key
guard passwordStore.pgpAgent.isImported else {
guard PGPAgent.shared.isPrepared else {
let alertTitle = "CannotAddPassword".localize()
let alertMessage = "PgpKeyNotSet.".localize()
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)

View file

@ -221,7 +221,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
@objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
SharedDefaults[.isRememberPGPPassphraseOn] = rememberPGPPassphraseSwitch.isOn
if rememberPGPPassphraseSwitch.isOn == false {
passwordStore.pgpAgent.passphrase = nil
AppKeychain.shared.removeContent(for: Globals.pgpKeyPassphrase)
}
}

View file

@ -15,8 +15,8 @@ class PGPKeyArmorSettingTableViewController: AutoCellHeightUITableViewController
@IBOutlet weak var scanPublicKeyCell: UITableViewCell!
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
var pgpPassphrase: String?
let passwordStore = PasswordStore.shared
let keychain = AppKeychain.shared
class ScannedPGPKey {
static let maxNumberOfGif = 100
@ -91,8 +91,6 @@ class PGPKeyArmorSettingTableViewController: AutoCellHeightUITableViewController
override func viewDidLoad() {
super.viewDidLoad()
pgpPassphrase = passwordStore.pgpAgent.passphrase
scanPublicKeyCell?.textLabel?.text = "ScanPublicKeyQrCodes".localize()
scanPublicKeyCell?.textLabel?.textColor = Globals.blue
scanPublicKeyCell?.selectionStyle = .default
@ -116,7 +114,7 @@ class PGPKeyArmorSettingTableViewController: AutoCellHeightUITableViewController
let savePassphraseAlert = UIAlertController(title: "Passphrase".localize(), message: "WantToSavePassphrase?".localize(), preferredStyle: UIAlertController.Style.alert)
// no
savePassphraseAlert.addAction(UIAlertAction(title: "No".localize(), style: UIAlertAction.Style.default) { _ in
self.pgpPassphrase = nil
self.keychain.removeContent(for: Globals.pgpKeyPassphrase)
SharedDefaults[.isRememberPGPPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
@ -125,12 +123,12 @@ class PGPKeyArmorSettingTableViewController: AutoCellHeightUITableViewController
// ask for the passphrase
let alert = UIAlertController(title: "Passphrase".localize(), message: "FillInPgpPassphrase.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok".localize(), style: UIAlertAction.Style.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
self.keychain.add(string: alert.textFields?.first?.text, for: Globals.pgpKeyPassphrase)
SharedDefaults[.isRememberPGPPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase
textField.text = self.keychain.get(for: Globals.pgpKeyPassphrase)
textField.isSecureTextEntry = true
})
self.present(alert, animated: true, completion: nil)

View file

@ -13,14 +13,14 @@ class PGPKeySettingTableViewController: AutoCellHeightUITableViewController {
@IBOutlet weak var pgpPublicKeyURLTextField: UITextField!
@IBOutlet weak var pgpPrivateKeyURLTextField: UITextField!
var pgpPassphrase: String?
let passwordStore = PasswordStore.shared
let keychain = AppKeychain.shared
override func viewDidLoad() {
super.viewDidLoad()
pgpPublicKeyURLTextField.text = SharedDefaults[.pgpPublicKeyURL]?.absoluteString
pgpPrivateKeyURLTextField.text = SharedDefaults[.pgpPrivateKeyURL]?.absoluteString
pgpPassphrase = passwordStore.pgpAgent.passphrase
}
private func validatePGPKeyURL(input: String?) -> Bool {
@ -43,7 +43,7 @@ class PGPKeySettingTableViewController: AutoCellHeightUITableViewController {
let savePassphraseAlert = UIAlertController(title: "Passphrase".localize(), message: "WantToSavePassphrase?".localize(), preferredStyle: UIAlertController.Style.alert)
// no
savePassphraseAlert.addAction(UIAlertAction(title: "No".localize(), style: UIAlertAction.Style.default) { _ in
self.pgpPassphrase = nil
self.keychain.removeContent(for: Globals.pgpKeyPassphrase)
SharedDefaults[.isRememberPGPPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
@ -52,12 +52,12 @@ class PGPKeySettingTableViewController: AutoCellHeightUITableViewController {
// ask for the passphrase
let alert = UIAlertController(title: "Passphrase".localize(), message: "FillInPgpPassphrase.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok".localize(), style: UIAlertAction.Style.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
self.keychain.add(string: alert.textFields?.first?.text, for: Globals.pgpKeyPassphrase)
SharedDefaults[.isRememberPGPPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase
textField.text = self.keychain.get(for: Globals.pgpKeyPassphrase)
textField.isSecureTextEntry = true
})
self.present(alert, animated: true, completion: nil)

View file

@ -103,7 +103,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
let _ = sem.wait(timeout: DispatchTime.distantFuture)
if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpAgent.passphrase = passphrase
AppKeychain.shared.add(string: passphrase, for: Globals.pgpKeyPassphrase)
}
return passphrase
}
@ -122,7 +122,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} catch {
DispatchQueue.main.async {
// remove the wrong passphrase so that users could enter it next time
self.passwordStore.pgpAgent.passphrase = nil
AppKeychain.shared.removeContent(for: Globals.pgpKeyPassphrase)
// alert: cancel or try again
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: error.localizedDescription, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Cancel".localize(), style: UIAlertAction.Style.default) { _ in

View file

@ -27,6 +27,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil
private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared
private var tapTabBarTime: TimeInterval = 0
@ -385,13 +386,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
SVProgressHUD.show(withStatus: "Decrypting".localize())
}
if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpAgent.passphrase = passphrase
keychain.add(string: passphrase, for: Globals.pgpKeyPassphrase)
}
return passphrase
}
private func decryptThenCopyPassword(from indexPath: IndexPath) {
guard self.passwordStore.pgpAgent.isImported else {
guard PGPAgent.shared.isPrepared else {
Utils.alert(title: "CannotCopyPassword".localize(), message: "SetPgpKey.".localize(), controller: self, completion: nil)
return
}
@ -412,7 +413,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} catch {
DispatchQueue.main.async {
// remove the wrong passphrase so that users could enter it next time
self.passwordStore.pgpAgent.passphrase = nil
self.keychain.removeContent(for: Globals.pgpKeyPassphrase)
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
}
}
@ -453,7 +454,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "showPasswordDetail" {
guard self.passwordStore.pgpAgent.isImported else {
guard PGPAgent.shared.isPrepared else {
Utils.alert(title: "CannotShowPassword".localize(), message: "SetPgpKey.".localize(), controller: self, completion: nil)
if let s = sender as? UITableViewCell {
let selectedIndexPath = tableView.indexPath(for: s)!
@ -462,7 +463,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return false
}
} else if identifier == "addPasswordSegue" {
guard self.passwordStore.pgpAgent.isImported && self.passwordStore.storeRepository != nil else {
guard PGPAgent.shared.isPrepared && self.passwordStore.storeRepository != nil else {
Utils.alert(title: "CannotAddPassword".localize(), message: "MakeSurePgpAndGitProperlySet.".localize(), controller: self, completion: nil)
return false
}

View file

@ -18,6 +18,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
var setPasscodeLockAlert: UIAlertController?
let passwordStore = PasswordStore.shared
let keychain = AppKeychain.shared
var passcodeLock = PasscodeLock.shared
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
@ -28,9 +29,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
if let controller = segue.source as? PGPKeySettingTableViewController {
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!.trimmed)
SharedDefaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!.trimmed)
if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpAgent.passphrase = controller.pgpPassphrase
}
SharedDefaults[.pgpKeySource] = "url"
SVProgressHUD.setDefaultMaskType(.black)
@ -38,10 +36,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
SVProgressHUD.show(withStatus: "FetchingPgpKey".localize())
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
try self.passwordStore.pgpAgent.initPGPKey(from: SharedDefaults[.pgpPublicKeyURL]!, keyType: .PUBLIC)
try self.passwordStore.pgpAgent.initPGPKey(from: SharedDefaults[.pgpPrivateKeyURL]!, keyType: .PRIVATE)
try KeyFileManager.PublicPgp.importKey(from: SharedDefaults[.pgpPublicKeyURL]!)
try KeyFileManager.PrivatePgp.importKey(from: SharedDefaults[.pgpPrivateKeyURL]!)
try PGPAgent.shared.initKeys()
DispatchQueue.main.async {
self.pgpKeyTableViewCell.detailTextLabel?.text = self.passwordStore.pgpAgent.pgpKeyID
self.pgpKeyTableViewCell.detailTextLabel?.text = PGPAgent.shared.keyId
SVProgressHUD.showSuccess(withStatus: "Success".localize())
SVProgressHUD.dismiss(withDelay: 1)
Utils.alert(title: "RememberToRemoveKey".localize(), message: "RememberToRemoveKeyFromServer.".localize(), controller: self, completion: nil)
@ -56,18 +55,17 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
SharedDefaults[.pgpKeySource] = "armor"
if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpAgent.passphrase = controller.pgpPassphrase
}
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.show(withStatus: "FetchingPgpKey".localize())
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
try self.passwordStore.pgpAgent.initPGPKey(with: controller.armorPublicKeyTextView.text ?? "", keyType: .PUBLIC)
try self.passwordStore.pgpAgent.initPGPKey(with: controller.armorPrivateKeyTextView.text ?? "", keyType: .PRIVATE)
try KeyFileManager.PublicPgp.importKey(from: controller.armorPublicKeyTextView.text ?? "")
try KeyFileManager.PrivatePgp.importKey(from: controller.armorPrivateKeyTextView.text ?? "")
try PGPAgent.shared.initKeys()
DispatchQueue.main.async {
self.pgpKeyTableViewCell.detailTextLabel?.text = self.passwordStore.pgpAgent.pgpKeyID
self.pgpKeyTableViewCell.detailTextLabel?.text = PGPAgent.shared.keyId
SVProgressHUD.showSuccess(withStatus: "Success".localize())
SVProgressHUD.dismiss(withDelay: 1)
}
@ -89,9 +87,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
SVProgressHUD.show(withStatus: "FetchingPgpKey".localize())
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
try self.passwordStore.pgpAgent.initPGPKeyFromFileSharing()
try KeyFileManager.PublicPgp.importKeyFromFileSharing()
try KeyFileManager.PrivatePgp.importKeyFromFileSharing()
try PGPAgent.shared.initKeys()
DispatchQueue.main.async {
self.pgpKeyTableViewCell.detailTextLabel?.text = self.passwordStore.pgpAgent.pgpKeyID
self.pgpKeyTableViewCell.detailTextLabel?.text = PGPAgent.shared.keyId
SVProgressHUD.showSuccess(withStatus: "Imported".localize())
SVProgressHUD.dismiss(withDelay: 1)
}
@ -135,11 +135,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
private func setPGPKeyTableViewCellDetailText() {
if let pgpKeyID = self.passwordStore.pgpAgent.pgpKeyID {
pgpKeyTableViewCell.detailTextLabel?.text = pgpKeyID
} else {
pgpKeyTableViewCell.detailTextLabel?.text = "NotSet".localize()
}
try? PGPAgent.shared.initKeys()
pgpKeyTableViewCell.detailTextLabel?.text = PGPAgent.shared.keyId ?? "NotSet".localize()
}
private func setPasswordRepositoryTableViewCellDetailText() {
@ -192,14 +189,14 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
optionMenu.addAction(urlAction)
optionMenu.addAction(armorAction)
if passwordStore.pgpAgent.isFileSharingReady {
if KeyFileManager.PublicPgp.doesKeyFileExist() && KeyFileManager.PrivatePgp.doesKeyFileExist() {
fileActionTitle.append(" (\("Import".localize()))")
let fileAction = UIAlertAction(title: fileActionTitle, style: .default) { _ in
// passphrase related
let savePassphraseAlert = UIAlertController(title: "Passphrase".localize(), message: "WantToSavePassphrase?".localize(), preferredStyle: UIAlertController.Style.alert)
// no
savePassphraseAlert.addAction(UIAlertAction(title: "No".localize(), style: UIAlertAction.Style.default) { _ in
self.passwordStore.pgpAgent.passphrase = nil
self.keychain.removeContent(for: Globals.pgpKeyPassphrase)
SharedDefaults[.isRememberPGPPassphraseOn] = false
self.saveImportedPGPKey()
})
@ -208,7 +205,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
// ask for the passphrase
let alert = UIAlertController(title: "Passphrase".localize(), message: "FillInPgpPassphrase.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok".localize(), style: UIAlertAction.Style.default, handler: {_ in
self.passwordStore.pgpAgent.passphrase = alert.textFields?.first?.text
self.keychain.add(string: alert.textFields?.first?.text, for: Globals.pgpKeyPassphrase)
SharedDefaults[.isRememberPGPPassphraseOn] = true
self.saveImportedPGPKey()
}))
@ -234,7 +231,9 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
if SharedDefaults[.pgpKeySource] != nil {
let deleteAction = UIAlertAction(title: "RemovePgpKeys".localize(), style: .destructive) { _ in
self.passwordStore.removePGPKeys()
self.keychain.removeContent(for: PgpKey.PUBLIC.getKeychainKey())
self.keychain.removeContent(for: PgpKey.PRIVATE.getKeychainKey())
PGPAgent.shared.uninitKeys()
self.pgpKeyTableViewCell.detailTextLabel?.text = "NotSet".localize()
}
optionMenu.addAction(deleteAction)

View file

@ -66,6 +66,7 @@
"PGPPublicKeyNotExistError." = "Der öffentliche PGP-Schlüssen existiert nicht";
"WrongPasswordFilename." = "Schreiben der Passwort-Datei nicht möglich .";
"DecryptionError." = "Passwort kann nicht entschlüsselt werden.";
"EncodingError." = "Schlüssel ist nicht in ASCII kodiert.";
"UnknownError." = "Ein unbekannter Fehler ist aufgetreten.";
"PrepareRepository" = "Repository vorbereiten";
"CheckingOutBranch" = "Branch '%@' wird ausgecheckt";

View file

@ -67,6 +67,7 @@
"PgpPublicKeyNotExistError." = "PGP public key doesn't exist.";
"WrongPasswordFilenameError." = "Cannot write to the password file.";
"DecryptionError." = "Cannot decrypt password.";
"EncodingError." = "Key is not ASCII encoded.";
"UnknownError." = "Unknown error.";
"PrepareRepository" = "Prepare Repository";
"CheckingOutBranch" = "Checking out branch '%@'";

View file

@ -145,7 +145,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath)
guard self.passwordStore.pgpAgent.isImported else {
guard PGPAgent.shared.isPrepared else {
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
return
}
@ -165,7 +165,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
} catch {
DispatchQueue.main.async {
// remove the wrong passphrase so that users could enter it next time
self.passwordStore.pgpAgent.passphrase = nil
AppKeychain.shared.removeContent(for: Globals.pgpKeyPassphrase)
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
}
}
@ -200,7 +200,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
}
let _ = sem.wait(timeout: DispatchTime.distantFuture)
if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpAgent.passphrase = passphrase
AppKeychain.shared.add(string: passphrase, for: Globals.pgpKeyPassphrase)
}
return passphrase
}

View file

@ -28,6 +28,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
@IBOutlet weak var tableView: UITableView!
private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared
private var searchActive = false
private var passwordsTableEntries: [PasswordsTableEntry] = []
@ -153,7 +154,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath)
guard self.passwordStore.pgpAgent.isImported else {
guard PGPAgent.shared.isPrepared else {
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
return
}
@ -191,7 +192,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
} catch {
DispatchQueue.main.async {
// remove the wrong passphrase so that users could enter it next time
self.passwordStore.pgpAgent.passphrase = nil
self.keychain.removeContent(for: Globals.pgpKeyPassphrase)
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
}
}
@ -227,7 +228,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}
let _ = sem.wait(timeout: DispatchTime.distantFuture)
if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpAgent.passphrase = passphrase
self.keychain.add(string: passphrase, for: Globals.pgpKeyPassphrase)
}
return passphrase
}

View file

@ -0,0 +1,57 @@
//
// GopenPgp.swift
// passKit
//
// Created by Danny Moesch on 08.09.19.
// Copyright © 2019 Bob Sun. All rights reserved.
//
import Crypto
struct GopenPgp: PgpInterface {
private let publicKey: CryptoKeyRing
private let privateKey: CryptoKeyRing
init(publicArmoredKey: String, privateArmoredKey: String) throws {
guard let pgp = CryptoGetGopenPGP() else {
throw AppError.KeyImport
}
publicKey = try pgp.buildKeyRingArmored(publicArmoredKey)
privateKey = try pgp.buildKeyRingArmored(privateArmoredKey)
}
func decrypt(encryptedData: Data, passphrase: String) throws -> Data? {
try privateKey.unlock(withPassphrase: passphrase)
let message = createPgpMessage(from: encryptedData)
return try privateKey.decrypt(message, verifyKey: nil, verifyTime: 0).data
}
func encrypt(plainData: Data) throws -> Data {
let encryptedData = try publicKey.encrypt(CryptoNewPlainMessage(plainData.mutable as Data), privateKey: nil)
if SharedDefaults[.encryptInArmored] {
var error: NSError?
let armor = encryptedData.getArmored(&error)
guard error == nil else {
throw error!
}
return armor.data(using: .ascii)!
}
return encryptedData.getBinary()!
}
var keyId: String {
var error: NSError?
let fingerprint = publicKey.getFingerprint(&error)
return error == nil ? String(fingerprint.suffix(8)).uppercased() : ""
}
private func createPgpMessage(from encryptedData: Data) -> CryptoPGPMessage? {
if SharedDefaults[.encryptInArmored] {
var error: NSError?
let message = CryptoNewPGPMessageFromArmored(String(data: encryptedData, encoding: .ascii), &error)
return error == nil ? message : nil
}
return CryptoNewPGPMessage(encryptedData.mutable as Data)
}
}

View file

@ -0,0 +1,48 @@
//
// ObjectivePgp.swift
// passKit
//
// Created by Danny Moesch on 08.09.19.
// Copyright © 2019 Bob Sun. All rights reserved.
//
import ObjectivePGP
struct ObjectivePgp: PgpInterface {
private let publicKey: Key
private let privateKey: Key
private let keyring = ObjectivePGP.defaultKeyring
init(publicArmoredKey: String, privateArmoredKey: String) throws {
guard let publicKeyData = publicArmoredKey.data(using: .ascii), let privateKeyData = privateArmoredKey.data(using: .ascii) else {
throw AppError.KeyImport
}
let publicKeys = try ObjectivePGP.readKeys(from: publicKeyData)
let privateKeys = try ObjectivePGP.readKeys(from: privateKeyData)
keyring.import(keys: publicKeys)
keyring.import(keys: privateKeys)
guard let publicKey = publicKeys.first, let privateKey = privateKeys.first else {
throw AppError.KeyImport
}
self.publicKey = publicKey
self.privateKey = privateKey
}
func decrypt(encryptedData: Data, passphrase: String) throws -> Data? {
return try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys) { _ in passphrase }
}
func encrypt(plainData: Data) throws -> Data {
let encryptedData = try ObjectivePGP.encrypt(plainData, addSignature: false, using: keyring.keys, passphraseForKey: nil)
if SharedDefaults[.encryptInArmored] {
return Armor.armored(encryptedData, as: .message).data(using: .ascii)!
}
return encryptedData
}
var keyId: String {
return publicKey.keyID.shortIdentifier
}
}

View file

@ -0,0 +1,64 @@
//
// PGPAgent.swift
// passKit
//
// Created by Yishi Lin on 2019/7/17.
// Copyright © 2019 Bob Sun. All rights reserved.
//
public class PGPAgent {
public static let shared: PGPAgent = PGPAgent()
private let keyStore: KeyStore
private var pgpInterface: PgpInterface?
public init(keyStore: KeyStore = AppKeychain.shared) {
self.keyStore = keyStore
}
public func initKeys() throws {
guard let publicKey: String = keyStore.get(for: PgpKey.PUBLIC.getKeychainKey()),
let privateKey: String = keyStore.get(for: PgpKey.PRIVATE.getKeychainKey()) else {
throw AppError.KeyImport
}
do {
pgpInterface = try GopenPgp(publicArmoredKey: publicKey, privateArmoredKey: privateKey)
} catch {
pgpInterface = try ObjectivePgp(publicArmoredKey: publicKey, privateArmoredKey: privateKey)
}
}
public func uninitKeys() {
pgpInterface = nil
}
public var keyId: String? {
return pgpInterface?.keyId
}
public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: () -> String) throws -> Data? {
try checkAndInit()
let passphrase = keyStore.get(for: Globals.pgpKeyPassphrase) ?? requestPGPKeyPassphrase()
return try pgpInterface!.decrypt(encryptedData: encryptedData, passphrase: passphrase)
}
public func encrypt(plainData: Data) throws -> Data {
try checkAndInit()
guard let pgpInterface = pgpInterface else {
throw AppError.Encryption
}
return try pgpInterface.encrypt(plainData: plainData)
}
public var isPrepared: Bool {
return keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey())
&& keyStore.contains(key: PgpKey.PRIVATE.getKeychainKey())
}
private func checkAndInit() throws {
if pgpInterface == nil || !keyStore.contains(key: Globals.pgpKeyPassphrase) {
try initKeys()
}
}
}

View file

@ -0,0 +1,16 @@
//
// PgpInterface.swift
// passKit
//
// Created by Danny Moesch on 08.09.19.
// Copyright © 2019 Bob Sun. All rights reserved.
//
protocol PgpInterface {
func decrypt(encryptedData: Data, passphrase: String) throws -> Data?
func encrypt(plainData: Data) throws -> Data
var keyId: String { get }
}

View file

@ -20,6 +20,7 @@ public enum AppError: Error, Equatable {
case WrongPasswordFilename
case Decryption
case Encryption
case Encoding
case Unknown
}

View file

@ -29,6 +29,8 @@ public class Globals {
public static let iTunesFileSharingPGPPrivate = iTunesFileSharingPath + "/gpg_key"
public static let iTunesFileSharingSSHPrivate = iTunesFileSharingPath + "/ssh_key"
public static let pgpKeyPassphrase = "pgpKeyPassphrase"
public static let gitSignatureDefaultName = "Pass for iOS"
public static let gitSignatureDefaultEmail = "user@passforios"

View file

@ -7,7 +7,7 @@
//
public class KeyFileManager {
public typealias KeyHandler = (Data, String) -> ()
public typealias KeyHandler = (String, String) -> Void
public static let PublicPgp = KeyFileManager(keyType: PgpKey.PUBLIC)
public static let PrivatePgp = KeyFileManager(keyType: PgpKey.PRIVATE)
@ -15,24 +15,35 @@ public class KeyFileManager {
private let keyType: CryptographicKey
private let keyPath: String
private let keyHandler: KeyHandler
private convenience init(keyType: CryptographicKey) {
self.init(keyType: keyType, keyPath: keyType.getFileSharingPath())
}
public init(keyType: CryptographicKey, keyPath: String) {
public init(keyType: CryptographicKey, keyPath: String, keyHandler: @escaping KeyHandler = AppKeychain.shared.add) {
self.keyType = keyType
self.keyPath = keyPath
self.keyHandler = keyHandler
}
public func importKeyAndDeleteFile(keyHandler: KeyHandler = AppKeychain.shared.add) throws {
guard let keyFileContent = FileManager.default.contents(atPath: keyPath) else {
throw AppError.ReadingFile(URL(fileURLWithPath: keyPath).lastPathComponent)
}
keyHandler(keyFileContent, keyType.getKeychainKey())
public func importKeyFromFileSharing() throws {
let keyFileContent = try String(contentsOfFile: keyPath, encoding: .ascii)
try importKey(from: keyFileContent)
try FileManager.default.removeItem(atPath: keyPath)
}
public func importKey(from string: String) throws {
guard string.unicodeScalars.allSatisfy({ $0.isASCII }) else {
throw AppError.Encoding
}
keyHandler(string, keyType.getKeychainKey())
}
public func importKey(from url: URL) throws {
try importKey(from: String(contentsOf: url, encoding: .ascii))
}
public func doesKeyFileExist() -> Bool {
return FileManager.default.fileExists(atPath: keyPath)
}

View file

@ -1,223 +0,0 @@
//
// PGPAgent.swift
// passKit
//
// Created by Yishi Lin on 2019/7/17.
// Copyright © 2019 Bob Sun. All rights reserved.
//
import Foundation
import ObjectivePGP
import KeychainAccess
import Crypto
public class PGPAgent {
private let keyStore: KeyStore
public init(keyStore: KeyStore = AppKeychain.shared) {
self.keyStore = keyStore
}
public var pgpKeyID: String?
// PGP passphrase
public var passphrase: String? {
set {
keyStore.add(string: newValue, for: "pgpKeyPassphrase")
}
get {
return keyStore.get(for: "pgpKeyPassphrase")
}
}
// Gopenpgpwrapper
private var publicKey: CryptoKeyRing? {
didSet {
var err: NSError? = nil
let fp = publicKey?.getFingerprint(&err)
if err == nil && fp != nil {
pgpKeyID = String(fp!.suffix(8)).uppercased()
} else {
pgpKeyID = ""
}
}
}
private var privateKey: CryptoKeyRing?
// ObjectivePGP
private let keyring = ObjectivePGP.defaultKeyring
private var publicKeyV2: Key? {
didSet {
pgpKeyID = publicKeyV2?.keyID.shortIdentifier
}
}
private var privateKeyV2: Key?
public var isImported: Bool {
get {
return (publicKey != nil || publicKeyV2 != nil) && (privateKey != nil || privateKeyV2 != nil)
}
}
public var isFileSharingReady: Bool {
get {
return KeyFileManager.PublicPgp.doesKeyFileExist() && KeyFileManager.PrivatePgp.doesKeyFileExist()
}
}
public func initPGPKeys() throws {
try initPGPKey(.PUBLIC)
try initPGPKey(.PRIVATE)
}
public func initPGPKey(_ keyType: PgpKey) throws {
// Clean up the previously set public/private key.
switch keyType {
case .PUBLIC:
self.publicKey = nil
self.publicKeyV2 = nil
case .PRIVATE:
self.privateKey = nil
self.privateKeyV2 = nil
}
// Read the key data from keychain.
guard let pgpKeyData: Data = keyStore.get(for: keyType.getKeychainKey()) else {
throw AppError.KeyImport
}
// Remove the key data from keychain temporary, in case the following step crashes repeatedly.
keyStore.removeContent(for: keyType.getKeychainKey())
// Try GopenPGP first.
let pgp = CryptoGetGopenPGP()
// Treat keys as binary first
if let key = try? pgp?.buildKeyRing(pgpKeyData) {
switch keyType {
case .PUBLIC:
self.publicKey = key
case .PRIVATE:
self.privateKey = key
}
keyStore.add(data: pgpKeyData, for: keyType.getKeychainKey())
return
}
// Treat key as ASCII armored keys if binary fails
if let key = try? pgp?.buildKeyRingArmored(String(data: pgpKeyData, encoding: .ascii)) {
switch keyType {
case .PUBLIC:
self.publicKey = key
case .PRIVATE:
self.privateKey = key
}
keyStore.add(data: pgpKeyData, for: keyType.getKeychainKey())
return
}
// Try ObjectivePGP as a backup plan.
// [ObjectivePGP.readKeys MAY CRASH!!!]
if let keys = try? ObjectivePGP.readKeys(from: pgpKeyData),
let key = keys.first {
keyring.import(keys: keys)
switch keyType {
case .PUBLIC:
self.publicKeyV2 = key
case .PRIVATE:
self.privateKeyV2 = key
}
keyStore.add(data: pgpKeyData, for: keyType.getKeychainKey())
return
}
throw AppError.KeyImport
}
public func initPGPKey(from url: URL, keyType: PgpKey) throws {
let pgpKeyData = try Data(contentsOf: url)
keyStore.add(data: pgpKeyData, for: keyType.getKeychainKey())
try initPGPKey(keyType)
}
public func initPGPKey(with armorKey: String, keyType: PgpKey) throws {
let pgpKeyData = armorKey.data(using: .ascii)!
keyStore.add(data: pgpKeyData, for: keyType.getKeychainKey())
try initPGPKey(keyType)
}
public func initPGPKeyFromFileSharing() throws {
try KeyFileManager.PublicPgp.importKeyAndDeleteFile(keyHandler: keyStore.add)
try KeyFileManager.PrivatePgp.importKeyAndDeleteFile(keyHandler: keyStore.add)
try initPGPKeys()
}
public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: () -> String) throws -> Data? {
guard privateKey != nil || privateKeyV2 != nil else {
throw AppError.PgpPublicKeyNotExist
}
let passphrase = self.passphrase ?? requestPGPKeyPassphrase()
// Try Gopenpgp.
if privateKey != nil {
try privateKey?.unlock(withPassphrase: passphrase)
var err : NSError? = nil
var message = CryptoNewPGPMessageFromArmored(String(data: encryptedData, encoding: .ascii), &err)
if err != nil {
message = CryptoNewPGPMessage(encryptedData)
}
if let decryptedData = try? privateKey?.decrypt(message, verifyKey: nil, verifyTime: 0) {
return decryptedData.data
}
}
// Try ObjectivePGP.
if privateKeyV2 != nil {
if let decryptedData = try? ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys, passphraseForKey: {(_) in passphrase}) {
return decryptedData
}
}
throw AppError.Decryption
}
public func encrypt(plainData: Data) throws -> Data {
guard publicKey != nil || publicKeyV2 != nil else {
throw AppError.PgpPublicKeyNotExist
}
// Try Gopenpgp.
if publicKey != nil {
if let encryptedData = try? publicKey?.encrypt(CryptoNewPlainMessageFromString(String(data: plainData, encoding: .utf8)), privateKey: nil) {
if SharedDefaults[.encryptInArmored] {
var err : NSError? = nil
let armor = encryptedData.getArmored(&err)
if err == nil {
return armor.data(using: .ascii)!
}
} else {
return encryptedData.getBinary()!
}
}
}
// Try ObjectivePGP.
if publicKeyV2 != nil {
if let encryptedData = try? ObjectivePGP.encrypt(plainData, addSignature: false, using: keyring.keys, passphraseForKey: nil) {
if SharedDefaults[.encryptInArmored] {
return Armor.armored(encryptedData, as: .message).data(using: .utf8)!
} else {
return encryptedData
}
}
}
throw AppError.Encryption
}
public func removePGPKeys() {
keyStore.removeContent(for: PgpKey.PUBLIC.getKeychainKey())
keyStore.removeContent(for: PgpKey.PRIVATE.getKeychainKey())
passphrase = nil
publicKey = nil
privateKey = nil
publicKeyV2 = nil
privateKeyV2 = nil
keyring.deleteAll()
}
}

View file

@ -25,8 +25,6 @@ public class PasswordStore {
public let storeURL = URL(fileURLWithPath: "\(Globals.repositoryPath)")
public let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
public let pgpAgent = PGPAgent()
public var storeRepository: GTRepository?
public var gitSignatureForNow: GTSignature? {
@ -111,7 +109,6 @@ public class PasswordStore {
if fm.fileExists(atPath: storeURL.path) {
try storeRepository = GTRepository.init(url: storeURL)
}
try self.pgpAgent.initPGPKeys()
} catch {
print(error)
}
@ -119,9 +116,9 @@ public class PasswordStore {
private func importExistingKeysIntoKeychain() {
// App Store update: v0.5.1 -> v0.6.0
try? KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: Globals.pgpPublicKeyPath).importKeyAndDeleteFile()
try? KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: Globals.pgpPrivateKeyPath).importKeyAndDeleteFile()
try? KeyFileManager(keyType: SshKey.PRIVATE, keyPath: Globals.gitSSHPrivateKeyPath).importKeyAndDeleteFile()
try? KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: Globals.pgpPublicKeyPath).importKeyFromFileSharing()
try? KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: Globals.pgpPrivateKeyPath).importKeyFromFileSharing()
try? KeyFileManager(keyType: SshKey.PRIVATE, keyPath: Globals.gitSSHPrivateKeyPath).importKeyFromFileSharing()
SharedDefaults.remove(.pgpPublicKeyArmor)
SharedDefaults.remove(.pgpPrivateKeyArmor)
SharedDefaults.remove(.gitSSHPrivateKeyArmor)
@ -638,7 +635,8 @@ public class PasswordStore {
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
self.pgpAgent.removePGPKeys()
AppKeychain.shared.removeContent(for: PgpKey.PUBLIC.getKeychainKey())
AppKeychain.shared.removeContent(for: PgpKey.PRIVATE.getKeychainKey())
AppKeychain.shared.removeAllContent()
@ -699,7 +697,7 @@ public class PasswordStore {
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
let encryptedData = try Data(contentsOf: encryptedDataPath)
guard let decryptedData = try self.pgpAgent.decrypt(encryptedData: encryptedData, requestPGPKeyPassphrase: requestPGPKeyPassphrase) else {
guard let decryptedData = try PGPAgent.shared.decrypt(encryptedData: encryptedData, requestPGPKeyPassphrase: requestPGPKeyPassphrase) else {
throw AppError.Decryption
}
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
@ -708,11 +706,7 @@ public class PasswordStore {
}
public func encrypt(password: Password) throws -> Data {
return try self.pgpAgent.encrypt(plainData: password.plainData)
}
public func removePGPKeys() {
self.pgpAgent.removePGPKeys()
return try PGPAgent.shared.encrypt(plainData: password.plainData)
}
public func removeGitSSHKeys() {
@ -725,6 +719,6 @@ public class PasswordStore {
}
public func gitSSHKeyImportFromFileSharing() throws {
try KeyFileManager.PrivateSsh.importKeyAndDeleteFile()
try KeyFileManager.PrivateSsh.importKeyFromFileSharing()
}
}

View file

@ -0,0 +1,105 @@
//
// PGPAgent.swift
// passKitTests
//
// Created by Yishi Lin on 2019/7/17.
// Copyright © 2019 Bob Sun. All rights reserved.
//
import XCTest
@testable import passKit
class PGPAgentTest: XCTestCase {
private var keychain: KeyStore!
private var pgpAgent: PGPAgent!
private let testData = "Hello World!".data(using: .utf8)!
override func setUp() {
super.setUp()
keychain = DictBasedKeychain()
pgpAgent = PGPAgent(keyStore: keychain)
}
override func tearDown() {
keychain.removeAllContent()
super.tearDown()
}
func basicEncryptDecrypt(using pgpAgent: PGPAgent) throws -> Data? {
let encryptedData = try pgpAgent.encrypt(plainData: testData)
return try pgpAgent.decrypt(encryptedData: encryptedData, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
}
func testBasicEncryptDecrypt() throws {
try [
RSA2048,
RSA2048_SUB,
ED25519,
//ED25519_SUB,
].forEach { keyTriple in
let keychain = DictBasedKeychain()
let pgpAgent = PGPAgent(keyStore: keychain)
try KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: keyTriple.publicKey)
try KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: keyTriple.privateKey)
XCTAssert(pgpAgent.isPrepared)
try pgpAgent.initKeys()
XCTAssert(pgpAgent.keyId!.lowercased().hasSuffix(keyTriple.fingerprint))
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent), testData)
}
}
func testNoPrivateKey() throws {
try KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: RSA2048.publicKey)
XCTAssertFalse(pgpAgent.isPrepared)
XCTAssertThrowsError(try pgpAgent.initKeys()) {
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
}
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
}
}
func testInterchangePublicAndPrivateKey() throws {
try importKeys(RSA2048.privateKey, RSA2048.publicKey)
XCTAssert(pgpAgent.isPrepared)
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
XCTAssert($0.localizedDescription.contains("gopenpgp: cannot unlock key ring, no private key available"))
}
}
func testIncompatibleKeyTypes() throws {
try importKeys(ED25519.publicKey, RSA2048.privateKey)
XCTAssert(pgpAgent.isPrepared)
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
XCTAssert($0.localizedDescription.contains("openpgp: incorrect key"))
}
}
func testCorruptedKey() throws {
try importKeys(RSA2048.publicKey.replacingOccurrences(of: "1", with: ""), RSA2048.privateKey)
XCTAssert(pgpAgent.isPrepared)
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
XCTAssert($0.localizedDescription.contains("Can't read keys. Invalid input."))
}
}
func testUnsettKeys() throws {
try importKeys(ED25519.publicKey, ED25519.privateKey)
XCTAssert(pgpAgent.isPrepared)
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent), testData)
keychain.removeContent(for: PgpKey.PUBLIC.getKeychainKey())
keychain.removeContent(for: PgpKey.PRIVATE.getKeychainKey())
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
}
}
private func importKeys(_ publicKey: String, _ privateKey: String) throws {
try KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: publicKey)
try KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: privateKey)
}
}

View file

@ -12,28 +12,54 @@ import XCTest
class KeyFileManagerTest: XCTestCase {
private static let filePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("test.txt").path
private static let keyFileManager = KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: filePath)
private static let keyFileManager = KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: filePath) { _, _ in }
override func tearDown() {
try? FileManager.default.removeItem(atPath: KeyFileManagerTest.filePath)
super.tearDown()
}
func testImportKeyAndDeleteFile() throws {
func testImportKeyFromFileSharing() throws {
let fileContent = "content".data(using: .ascii)
var storage: [String: Data] = [:]
let keyFileManager = KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: KeyFileManagerTest.filePath)
var storage: [String: String] = [:]
let keyFileManager = KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: KeyFileManagerTest.filePath) { storage[$1] = $0 }
FileManager.default.createFile(atPath: KeyFileManagerTest.filePath, contents: fileContent, attributes: nil)
try keyFileManager.importKeyAndDeleteFile { storage[$1] = $0 }
try keyFileManager.importKeyFromFileSharing()
XCTAssertFalse(FileManager.default.fileExists(atPath: KeyFileManagerTest.filePath))
XCTAssertTrue(storage[PgpKey.PRIVATE.getKeychainKey()] == fileContent)
XCTAssertEqual(storage[PgpKey.PRIVATE.getKeychainKey()], "content")
}
func testErrorReadingFile() throws {
XCTAssertThrowsError(try KeyFileManagerTest.keyFileManager.importKeyAndDeleteFile { _, _ in }) {
XCTAssertEqual($0 as! AppError, AppError.ReadingFile("test.txt"))
XCTAssertThrowsError(try KeyFileManagerTest.keyFileManager.importKeyFromFileSharing())
}
func testImportKeyFromUrl() throws {
let fileContent = "content".data(using: .ascii)
let url = URL(fileURLWithPath: KeyFileManagerTest.filePath)
var storage: [String: String] = [:]
let keyFileManager = KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: KeyFileManagerTest.filePath) { storage[$1] = $0 }
FileManager.default.createFile(atPath: KeyFileManagerTest.filePath, contents: fileContent, attributes: nil)
try keyFileManager.importKey(from: url)
XCTAssertEqual(storage[PgpKey.PRIVATE.getKeychainKey()], "content")
}
func testImportKeyFromString() throws {
let string = "content"
var storage: [String: String] = [:]
let keyFileManager = KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: KeyFileManagerTest.filePath) { storage[$1] = $0 }
try keyFileManager.importKey(from: string)
XCTAssertEqual(storage[PgpKey.PRIVATE.getKeychainKey()], string)
}
func testImportKeyFromNonAsciiString() throws {
XCTAssertThrowsError(try KeyFileManagerTest.keyFileManager.importKey(from: "")) {
XCTAssertEqual($0 as! AppError, AppError.Encoding)
}
}

View file

@ -1,108 +0,0 @@
//
// PGPAgent.swift
// passKitTests
//
// Created by Yishi Lin on 2019/7/17.
// Copyright © 2019 Bob Sun. All rights reserved.
//
import XCTest
@testable import passKit
class PGPAgentTest: XCTestCase {
private let keychain = DictBasedKeychain()
func basicEncryptDecrypt(pgpAgent: PGPAgent) -> Bool {
// Encrypt and decrypt.
let plainData = "Hello World!".data(using: .utf8)!
guard let encryptedData = try? pgpAgent.encrypt(plainData: plainData) else {
return false
}
guard let decryptedData = try? pgpAgent.decrypt(encryptedData: encryptedData, requestPGPKeyPassphrase: requestPGPKeyPassphrase) else {
return false
}
return plainData == decryptedData
}
func testInitPGPKey() {
let pgpAgent = PGPAgent(keyStore: keychain)
// [RSA2048] Setup keys.
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PUBLIC_KEY, keyType: .PUBLIC)
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PRIVATE_KEY, keyType: .PRIVATE)
XCTAssertTrue(pgpAgent.isImported)
XCTAssertEqual(pgpAgent.pgpKeyID, "A1024DAE")
XCTAssertTrue(self.basicEncryptDecrypt(pgpAgent: pgpAgent))
let pgpAgent2 = PGPAgent(keyStore: keychain)
try? pgpAgent2.initPGPKeys() // load from the keychain
XCTAssertTrue(self.basicEncryptDecrypt(pgpAgent: pgpAgent2))
pgpAgent.removePGPKeys()
// [RSA2048] Setup keys. The private key is a subkey.
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PUBLIC_KEY, keyType: .PUBLIC)
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PRIVATE_SUBKEY, keyType: .PRIVATE)
XCTAssertTrue(pgpAgent.isImported)
XCTAssertEqual(pgpAgent.pgpKeyID, "A1024DAE")
XCTAssertTrue(self.basicEncryptDecrypt(pgpAgent: pgpAgent))
pgpAgent.removePGPKeys()
// [ED25519] Setup keys.
try? pgpAgent.initPGPKey(with: PGP_ED25519_PUBLIC_KEY, keyType: .PUBLIC)
try? pgpAgent.initPGPKey(with: PGP_ED25519_PRIVATE_KEY, keyType: .PRIVATE)
XCTAssertTrue(pgpAgent.isImported)
XCTAssertEqual(pgpAgent.pgpKeyID, "E9444483")
XCTAssertTrue(self.basicEncryptDecrypt(pgpAgent: pgpAgent))
pgpAgent.removePGPKeys()
// [RSA2048] Setup keys from URL.
let publicKeyURL = URL(fileURLWithPath: PgpKey.PUBLIC.getFileSharingPath())
let privateKeyURL = URL(fileURLWithPath: PgpKey.PRIVATE.getFileSharingPath())
try? PGP_RSA2048_PUBLIC_KEY.write(to: publicKeyURL, atomically: false, encoding: .utf8)
try? PGP_RSA2048_PRIVATE_KEY.write(to: privateKeyURL, atomically: false, encoding: .utf8)
try? pgpAgent.initPGPKey(from: publicKeyURL, keyType: .PUBLIC)
try? pgpAgent.initPGPKey(from: privateKeyURL, keyType: .PRIVATE)
XCTAssertTrue(pgpAgent.isImported)
XCTAssertEqual(pgpAgent.pgpKeyID, "A1024DAE")
XCTAssertTrue(self.basicEncryptDecrypt(pgpAgent: pgpAgent))
pgpAgent.removePGPKeys()
// [RSA2048] Setup keys from iTunes file sharing.
try? PGP_RSA2048_PUBLIC_KEY.write(to: publicKeyURL, atomically: false, encoding: .utf8)
try? PGP_RSA2048_PRIVATE_KEY.write(to: privateKeyURL, atomically: false, encoding: .utf8)
XCTAssertTrue(pgpAgent.isFileSharingReady)
try? pgpAgent.initPGPKeyFromFileSharing()
XCTAssertTrue(pgpAgent.isImported)
XCTAssertEqual(pgpAgent.pgpKeyID, "A1024DAE")
XCTAssertTrue(self.basicEncryptDecrypt(pgpAgent: pgpAgent))
XCTAssertFalse(FileManager.default.fileExists(atPath: publicKeyURL.absoluteString))
XCTAssertFalse(FileManager.default.fileExists(atPath: privateKeyURL.absoluteString))
pgpAgent.removePGPKeys()
}
func testInitPGPKeyBadPrivateKeys() {
let pgpAgent = PGPAgent(keyStore: keychain)
let plainData = "Hello World!".data(using: .utf8)!
// [RSA2048] Setup the public key.
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PUBLIC_KEY, keyType: .PUBLIC)
let encryptedData = try? pgpAgent.encrypt(plainData: plainData)
XCTAssertNotNil(encryptedData)
XCTAssertThrowsError(try pgpAgent.decrypt(encryptedData: encryptedData!, requestPGPKeyPassphrase: requestPGPKeyPassphrase))
// Wrong private key: a public key.
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PUBLIC_KEY, keyType: .PRIVATE)
XCTAssertThrowsError(try pgpAgent.decrypt(encryptedData: encryptedData!, requestPGPKeyPassphrase: requestPGPKeyPassphrase))
// Wrong private key: an unmatched private key.
try? pgpAgent.initPGPKey(with: PGP_ED25519_PRIVATE_KEY, keyType: .PRIVATE)
XCTAssertThrowsError(try pgpAgent.decrypt(encryptedData: encryptedData!, requestPGPKeyPassphrase: requestPGPKeyPassphrase))
/// Wrong private key: a corrupted private key.
try? pgpAgent.initPGPKey(with: PGP_RSA2048_PRIVATE_KEY.replacingOccurrences(of: "1", with: ""), keyType: .PRIVATE)
XCTAssertThrowsError(try pgpAgent.decrypt(encryptedData: encryptedData!, requestPGPKeyPassphrase: requestPGPKeyPassphrase))
}
}