Add ignore .gpg-id switch default ON
This commit is contained in:
parent
6280b1522b
commit
a62792bd11
9 changed files with 91 additions and 13 deletions
|
|
@ -46,6 +46,15 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
return uiSwitch
|
return uiSwitch
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
let ignoreGPGIDSwitch: UISwitch = {
|
||||||
|
let uiSwitch = UISwitch()
|
||||||
|
uiSwitch.onTintColor = Colors.systemBlue
|
||||||
|
uiSwitch.sizeToFit()
|
||||||
|
uiSwitch.addTarget(self, action: #selector(ignoreGPGIDSwitchAction(_:)), for: UIControl.Event.valueChanged)
|
||||||
|
uiSwitch.isOn = Defaults.isIgnoreGPGIDOn
|
||||||
|
return uiSwitch
|
||||||
|
}()
|
||||||
|
|
||||||
let showFolderSwitch: UISwitch = {
|
let showFolderSwitch: UISwitch = {
|
||||||
let uiSwitch = UISwitch()
|
let uiSwitch = UISwitch()
|
||||||
uiSwitch.onTintColor = Colors.systemBlue
|
uiSwitch.onTintColor = Colors.systemBlue
|
||||||
|
|
@ -77,6 +86,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
|
|
||||||
// section 2
|
// section 2
|
||||||
[
|
[
|
||||||
|
[.title: "IgnoreGPGID".localize(), .action: "none"],
|
||||||
[.title: "ShowFolders".localize(), .action: "none"],
|
[.title: "ShowFolders".localize(), .action: "none"],
|
||||||
[.title: "HidePasswordImages".localize(), .action: "none"],
|
[.title: "HidePasswordImages".localize(), .action: "none"],
|
||||||
[.title: "HideUnknownFields".localize(), .action: "none"],
|
[.title: "HideUnknownFields".localize(), .action: "none"],
|
||||||
|
|
@ -129,6 +139,10 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
cell.accessoryType = .none
|
cell.accessoryType = .none
|
||||||
cell.selectionStyle = .none
|
cell.selectionStyle = .none
|
||||||
cell.accessoryView = showFolderSwitch
|
cell.accessoryView = showFolderSwitch
|
||||||
|
case "IgnoreGPGID".localize():
|
||||||
|
cell.accessoryType = .none
|
||||||
|
cell.selectionStyle = .none
|
||||||
|
cell.accessoryView = ignoreGPGIDSwitch
|
||||||
case "HidePasswordImages".localize():
|
case "HidePasswordImages".localize():
|
||||||
cell.accessoryType = .none
|
cell.accessoryType = .none
|
||||||
let detailButton = UIButton(type: .detailDisclosure)
|
let detailButton = UIButton(type: .detailDisclosure)
|
||||||
|
|
@ -206,6 +220,11 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
|
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func ignoreGPGIDSwitchAction(_: Any?) {
|
||||||
|
Defaults.isIgnoreGPGIDOn = ignoreGPGIDSwitch.isOn
|
||||||
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func hidePasswordImagesSwitchAction(_: Any?) {
|
func hidePasswordImagesSwitchAction(_: Any?) {
|
||||||
Defaults.isHidePasswordImagesOn = hidePasswordImagesSwitch.isOn
|
Defaults.isHidePasswordImagesOn = hidePasswordImagesSwitch.isOn
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
"PasswordGeneratorFlavor" = "Style";
|
"PasswordGeneratorFlavor" = "Style";
|
||||||
"RememberPgpKeyPassphrase" = "Remember PGP Key Passphrase";
|
"RememberPgpKeyPassphrase" = "Remember PGP Key Passphrase";
|
||||||
"RememberGitCredentialPassphrase" = "Remember Git Credential Passphrase";
|
"RememberGitCredentialPassphrase" = "Remember Git Credential Passphrase";
|
||||||
|
"IgnoreGPGID" = "Ignore .gpg-id";
|
||||||
"ShowFolders" = "Show Folders";
|
"ShowFolders" = "Show Folders";
|
||||||
"HidePasswordImages" = "Hide Password Images";
|
"HidePasswordImages" = "Hide Password Images";
|
||||||
"HidePasswordImagesExplanation." = "Associated favicon images are loaded and shown based upon the URL associated with an entry. Enable this option to hide these images and prevent them from being loaded.";
|
"HidePasswordImagesExplanation." = "Associated favicon images are loaded and shown based upon the URL associated with an entry. Enable this option to hide these images and prevent them from being loaded.";
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,16 @@ struct GopenPGPInterface: PGPInterface {
|
||||||
privateKeys.keys.contains { key in key.hasSuffix(keyID.lowercased()) }
|
privateKeys.keys.contains { key in key.hasSuffix(keyID.lowercased()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? {
|
func decrypt(encryptedData: Data, keyID: String?, passphrase: String) throws -> Data? {
|
||||||
guard let key = privateKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) }),
|
let key: CryptoKey? = {
|
||||||
let privateKey = privateKeys[key.key] else {
|
if let keyID = keyID {
|
||||||
|
return privateKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) })?.value
|
||||||
|
} else {
|
||||||
|
return privateKeys.first?.value
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
guard let privateKey = key else {
|
||||||
throw AppError.decryption
|
throw AppError.decryption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,9 +101,16 @@ struct GopenPGPInterface: PGPInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func encrypt(plainData: Data, keyID: String) throws -> Data {
|
func encrypt(plainData: Data, keyID: String?) throws -> Data {
|
||||||
guard let key = publicKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) }),
|
let key: CryptoKey? = {
|
||||||
let publicKey = publicKeys[key.key] else {
|
if let keyID = keyID {
|
||||||
|
return publicKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) })?.value
|
||||||
|
} else {
|
||||||
|
return publicKeys.first?.value
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
guard let publicKey = key else {
|
||||||
throw AppError.encryption
|
throw AppError.encryption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ struct ObjectivePGPInterface: PGPInterface {
|
||||||
self.privateKey = privateKey
|
self.privateKey = privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(encryptedData: Data, keyID _: String, passphrase: String) throws -> Data? {
|
func decrypt(encryptedData: Data, keyID _: String?, passphrase: String) throws -> Data? {
|
||||||
try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys) { _ in passphrase }
|
try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys) { _ in passphrase }
|
||||||
}
|
}
|
||||||
|
|
||||||
func encrypt(plainData: Data, keyID _: String) throws -> Data {
|
func encrypt(plainData: Data, keyID _: String?) throws -> Data {
|
||||||
let encryptedData = try ObjectivePGP.encrypt(plainData, addSignature: false, using: keyring.keys, passphraseForKey: nil)
|
let encryptedData = try ObjectivePGP.encrypt(plainData, addSignature: false, using: keyring.keys, passphraseForKey: nil)
|
||||||
if Defaults.encryptInArmored {
|
if Defaults.encryptInArmored {
|
||||||
return Armor.armored(encryptedData, as: .message).data(using: .ascii)!
|
return Armor.armored(encryptedData, as: .message).data(using: .ascii)!
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,36 @@ public class PGPAgent {
|
||||||
return try pgpInterface.encrypt(plainData: plainData, keyID: keyID)
|
return try pgpInterface.encrypt(plainData: plainData, keyID: keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: (String) -> String) throws -> Data? {
|
||||||
|
// Remember the previous status and set the current status
|
||||||
|
let previousDecryptStatus = self.latestDecryptStatus
|
||||||
|
self.latestDecryptStatus = false
|
||||||
|
// Init keys.
|
||||||
|
try checkAndInit()
|
||||||
|
// Get the PGP key passphrase.
|
||||||
|
var passphrase = ""
|
||||||
|
if previousDecryptStatus == false {
|
||||||
|
passphrase = requestPGPKeyPassphrase("default")
|
||||||
|
} else {
|
||||||
|
passphrase = keyStore.get(for: Globals.pgpKeyPassphrase) ?? requestPGPKeyPassphrase("default")
|
||||||
|
}
|
||||||
|
// Decrypt.
|
||||||
|
guard let result = try pgpInterface!.decrypt(encryptedData: encryptedData, keyID: nil, passphrase: passphrase) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// The decryption step has succeed.
|
||||||
|
self.latestDecryptStatus = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encrypt(plainData: Data) throws -> Data {
|
||||||
|
try checkAndInit()
|
||||||
|
guard let pgpInterface = pgpInterface else {
|
||||||
|
throw AppError.encryption
|
||||||
|
}
|
||||||
|
return try pgpInterface.encrypt(plainData: plainData, keyID: nil)
|
||||||
|
}
|
||||||
|
|
||||||
public var isPrepared: Bool {
|
public var isPrepared: Bool {
|
||||||
keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey())
|
keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey())
|
||||||
&& keyStore.contains(key: PgpKey.PRIVATE.getKeychainKey())
|
&& keyStore.contains(key: PgpKey.PRIVATE.getKeychainKey())
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
protocol PGPInterface {
|
protocol PGPInterface {
|
||||||
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data?
|
func decrypt(encryptedData: Data, keyID: String?, passphrase: String) throws -> Data?
|
||||||
|
|
||||||
func encrypt(plainData: Data, keyID: String) throws -> Data
|
func encrypt(plainData: Data, keyID: String?) throws -> Data
|
||||||
|
|
||||||
func containsPublicKey(with keyID: String) -> Bool
|
func containsPublicKey(with keyID: String) -> Bool
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ public extension DefaultsKeys {
|
||||||
var isHideOTPOn: DefaultsKey<Bool> { .init("isHideOTPOn", defaultValue: false) }
|
var isHideOTPOn: DefaultsKey<Bool> { .init("isHideOTPOn", defaultValue: false) }
|
||||||
var isRememberPGPPassphraseOn: DefaultsKey<Bool> { .init("isRememberPGPPassphraseOn", defaultValue: false) }
|
var isRememberPGPPassphraseOn: DefaultsKey<Bool> { .init("isRememberPGPPassphraseOn", defaultValue: false) }
|
||||||
var isRememberGitCredentialPassphraseOn: DefaultsKey<Bool> { .init("isRememberGitCredentialPassphraseOn", defaultValue: false) }
|
var isRememberGitCredentialPassphraseOn: DefaultsKey<Bool> { .init("isRememberGitCredentialPassphraseOn", defaultValue: false) }
|
||||||
|
var isIgnoreGPGIDOn: DefaultsKey<Bool> { .init("isIgnoreGPGIDOn", defaultValue: true) }
|
||||||
var isShowFolderOn: DefaultsKey<Bool> { .init("isShowFolderOn", defaultValue: true) }
|
var isShowFolderOn: DefaultsKey<Bool> { .init("isShowFolderOn", defaultValue: true) }
|
||||||
var isHidePasswordImagesOn: DefaultsKey<Bool> { .init("isHidePasswordImagesOn", defaultValue: false) }
|
var isHidePasswordImagesOn: DefaultsKey<Bool> { .init("isHidePasswordImagesOn", defaultValue: false) }
|
||||||
var searchDefault: DefaultsKey<SearchBarScope?> { .init("searchDefault", defaultValue: .all) }
|
var searchDefault: DefaultsKey<SearchBarScope?> { .init("searchDefault", defaultValue: .all) }
|
||||||
|
|
|
||||||
|
|
@ -689,9 +689,16 @@ public class PasswordStore {
|
||||||
|
|
||||||
public func decrypt(passwordEntity: PasswordEntity, keyID: String? = nil, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Password {
|
public func decrypt(passwordEntity: PasswordEntity, keyID: String? = nil, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Password {
|
||||||
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||||
let keyID = keyID ?? findGPGID(from: encryptedDataPath)
|
|
||||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||||
guard let decryptedData = try PGPAgent.shared.decrypt(encryptedData: encryptedData, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase) else {
|
let data: Data? = try {
|
||||||
|
if Defaults.isIgnoreGPGIDOn {
|
||||||
|
return try PGPAgent.shared.decrypt(encryptedData: encryptedData, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
|
} else {
|
||||||
|
let keyID = keyID ?? findGPGID(from: encryptedDataPath)
|
||||||
|
return try PGPAgent.shared.decrypt(encryptedData: encryptedData, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
guard let decryptedData = data else {
|
||||||
throw AppError.decryption
|
throw AppError.decryption
|
||||||
}
|
}
|
||||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||||
|
|
@ -703,8 +710,12 @@ public class PasswordStore {
|
||||||
guard let passwordEntity = fetchPasswordEntity(with: path) else {
|
guard let passwordEntity = fetchPasswordEntity(with: path) else {
|
||||||
throw AppError.decryption
|
throw AppError.decryption
|
||||||
}
|
}
|
||||||
|
if Defaults.isIgnoreGPGIDOn {
|
||||||
|
return try decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
|
} else {
|
||||||
return try decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
return try decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func encrypt(password: Password, keyID: String? = nil) throws -> Data {
|
public func encrypt(password: Password, keyID: String? = nil) throws -> Data {
|
||||||
let encryptedDataPath = storeURL.appendingPathComponent(password.url.path)
|
let encryptedDataPath = storeURL.appendingPathComponent(password.url.path)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class PasswordStoreTest: XCTestCase {
|
||||||
|
|
||||||
func testCloneAndDecryptMultiKeys() throws {
|
func testCloneAndDecryptMultiKeys() throws {
|
||||||
let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test")
|
let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test")
|
||||||
|
Defaults.isIgnoreGPGIDOn = false
|
||||||
let passwordStore = PasswordStore(url: url)
|
let passwordStore = PasswordStore(url: url)
|
||||||
try passwordStore.cloneRepository(remoteRepoURL: remoteRepoURL, branchName: "master")
|
try passwordStore.cloneRepository(remoteRepoURL: remoteRepoURL, branchName: "master")
|
||||||
expectation(for: NSPredicate { _, _ in FileManager.default.fileExists(atPath: url.path) }, evaluatedWith: nil)
|
expectation(for: NSPredicate { _, _ in FileManager.default.fileExists(atPath: url.path) }, evaluatedWith: nil)
|
||||||
|
|
@ -46,6 +47,7 @@ class PasswordStoreTest: XCTestCase {
|
||||||
XCTAssertEqual(testPasswordPlain.plainText, "testpassword")
|
XCTAssertEqual(testPasswordPlain.plainText, "testpassword")
|
||||||
|
|
||||||
passwordStore.erase()
|
passwordStore.erase()
|
||||||
|
Defaults.isIgnoreGPGIDOn = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func decrypt(passwordStore: PasswordStore, path: String, passphrase _: String) throws -> Password {
|
private func decrypt(passwordStore: PasswordStore, path: String, passphrase _: String) throws -> Password {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue