Partially implement multikeys support (decryption)
This commit is contained in:
parent
7f6e3f1909
commit
b7ee00815c
7 changed files with 165 additions and 40 deletions
|
|
@ -15,23 +15,60 @@ struct GopenPgp: PgpInterface {
|
||||||
"openpgp: incorrect key": AppError.KeyExpiredOrIncompatible,
|
"openpgp: incorrect key": AppError.KeyExpiredOrIncompatible,
|
||||||
]
|
]
|
||||||
|
|
||||||
private let publicKey: CryptoKey
|
private var publicKeys: [String: CryptoKey] = [:]
|
||||||
private let privateKey: CryptoKey
|
private var privateKeys: [String: CryptoKey] = [:]
|
||||||
|
|
||||||
init(publicArmoredKey: String, privateArmoredKey: String) throws {
|
init(publicArmoredKey: String, privateArmoredKey: String) throws {
|
||||||
var error: NSError?
|
let pubKeys = extractKeysFromArmored(str: publicArmoredKey)
|
||||||
guard let publicKey = CryptoNewKeyFromArmored(publicArmoredKey, &error),
|
let prvKeys = extractKeysFromArmored(str: privateArmoredKey)
|
||||||
let privateKey = CryptoNewKeyFromArmored(privateArmoredKey, &error) else {
|
|
||||||
guard error == nil else {
|
for key in pubKeys {
|
||||||
throw error!
|
var error: NSError?
|
||||||
|
guard let k = CryptoNewKeyFromArmored(key, &error) else {
|
||||||
|
guard error == nil else {
|
||||||
|
throw error!
|
||||||
|
}
|
||||||
|
throw AppError.KeyImport
|
||||||
}
|
}
|
||||||
throw AppError.KeyImport
|
publicKeys[k.getFingerprint()] = k
|
||||||
}
|
}
|
||||||
self.publicKey = publicKey
|
|
||||||
self.privateKey = privateKey
|
for key in prvKeys {
|
||||||
|
var error: NSError?
|
||||||
|
guard let k = CryptoNewKeyFromArmored(key, &error) else {
|
||||||
|
guard error == nil else {
|
||||||
|
throw error!
|
||||||
|
}
|
||||||
|
throw AppError.KeyImport
|
||||||
|
}
|
||||||
|
privateKeys[k.getFingerprint()] = k
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractKeysFromArmored(str: String) -> [String] {
|
||||||
|
var keys: [String] = []
|
||||||
|
var key: String = ""
|
||||||
|
for line in str.splitByNewline() {
|
||||||
|
if line.trimmed.uppercased().hasPrefix("-----BEGIN PGP") {
|
||||||
|
key = ""
|
||||||
|
key += line + "\n"
|
||||||
|
} else if line.trimmed.uppercased().hasPrefix("-----END PGP") {
|
||||||
|
key += line
|
||||||
|
keys.append(key)
|
||||||
|
} else {
|
||||||
|
key += line + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? {
|
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? {
|
||||||
|
guard let e = privateKeys.first(where: { (key, _) in key.hasSuffix(keyID.lowercased()) }),
|
||||||
|
let privateKey = privateKeys[e.key] else {
|
||||||
|
throw AppError.Decryption
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let unlockedKey = try privateKey.unlock(passphrase.data(using: .utf8))
|
let unlockedKey = try privateKey.unlock(passphrase.data(using: .utf8))
|
||||||
var error: NSError?
|
var error: NSError?
|
||||||
|
|
@ -51,6 +88,11 @@ struct GopenPgp: PgpInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func encrypt(plainData: Data, keyID: String) throws -> Data {
|
func encrypt(plainData: Data, keyID: String) throws -> Data {
|
||||||
|
guard let e = publicKeys.first(where: { (key, _) in key.hasSuffix(keyID.lowercased()) }),
|
||||||
|
let publicKey = publicKeys[e.key] else {
|
||||||
|
throw AppError.Encryption
|
||||||
|
}
|
||||||
|
|
||||||
var error: NSError?
|
var error: NSError?
|
||||||
|
|
||||||
guard let keyRing = CryptoNewKeyRing(publicKey, &error) else {
|
guard let keyRing = CryptoNewKeyRing(publicKey, &error) else {
|
||||||
|
|
@ -73,14 +115,12 @@ struct GopenPgp: PgpInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyId: String {
|
var keyId: String {
|
||||||
var error: NSError?
|
let fingerprint = publicKeys.first?.key ?? ""
|
||||||
let fingerprint = publicKey.getHexKeyID()
|
|
||||||
return String(fingerprint).uppercased()
|
return String(fingerprint).uppercased()
|
||||||
}
|
}
|
||||||
|
|
||||||
var shortKeyId: String {
|
var shortKeyId: String {
|
||||||
var error: NSError?
|
let fingerprint = publicKeys.first?.key ?? ""
|
||||||
let fingerprint = publicKey.getHexKeyID()
|
|
||||||
return String(fingerprint.suffix(8)).uppercased()
|
return String(fingerprint.suffix(8)).uppercased()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public class PGPAgent {
|
||||||
return pgpInterface?.shortKeyId
|
return pgpInterface?.shortKeyId
|
||||||
}
|
}
|
||||||
|
|
||||||
public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: () -> String) throws -> Data? {
|
public func decrypt(encryptedData: Data, keyID: String, requestPGPKeyPassphrase: () -> String) throws -> Data? {
|
||||||
// Remember the previous status and set the current status
|
// Remember the previous status and set the current status
|
||||||
let previousDecryptStatus = self.latestDecryptStatus
|
let previousDecryptStatus = self.latestDecryptStatus
|
||||||
self.latestDecryptStatus = false
|
self.latestDecryptStatus = false
|
||||||
|
|
@ -59,7 +59,7 @@ public class PGPAgent {
|
||||||
passphrase = keyStore.get(for: Globals.pgpKeyPassphrase) ?? requestPGPKeyPassphrase()
|
passphrase = keyStore.get(for: Globals.pgpKeyPassphrase) ?? requestPGPKeyPassphrase()
|
||||||
}
|
}
|
||||||
// Decrypt.
|
// Decrypt.
|
||||||
guard let result = try pgpInterface!.decrypt(encryptedData: encryptedData, keyID: "", passphrase: passphrase) else {
|
guard let result = try pgpInterface!.decrypt(encryptedData: encryptedData, keyID: keyID, passphrase: passphrase) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// The decryption step has succeed.
|
// The decryption step has succeed.
|
||||||
|
|
@ -67,12 +67,12 @@ public class PGPAgent {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encrypt(plainData: Data) throws -> Data {
|
public func encrypt(plainData: Data, keyID: String) throws -> Data {
|
||||||
try checkAndInit()
|
try checkAndInit()
|
||||||
guard let pgpInterface = pgpInterface else {
|
guard let pgpInterface = pgpInterface else {
|
||||||
throw AppError.Encryption
|
throw AppError.Encryption
|
||||||
}
|
}
|
||||||
return try pgpInterface.encrypt(plainData: plainData, keyID: "")
|
return try pgpInterface.encrypt(plainData: plainData, keyID: keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isPrepared: Bool {
|
public var isPrepared: Bool {
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,8 @@ public class PasswordStore {
|
||||||
options: [AnyHashable : Any]? = nil,
|
options: [AnyHashable : Any]? = nil,
|
||||||
branchName: String,
|
branchName: String,
|
||||||
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
||||||
checkoutProgressBlock: @escaping (String, UInt, UInt) -> Void) throws {
|
checkoutProgressBlock: @escaping (String, UInt, UInt) -> Void,
|
||||||
|
completion: @escaping () -> Void = {}) throws {
|
||||||
try? fm.removeItem(at: storeURL)
|
try? fm.removeItem(at: storeURL)
|
||||||
try? fm.removeItem(at: tempStoreURL)
|
try? fm.removeItem(at: tempStoreURL)
|
||||||
self.gitPassword = nil
|
self.gitPassword = nil
|
||||||
|
|
@ -221,6 +222,7 @@ public class PasswordStore {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.updatePasswordEntityCoreData()
|
self.updatePasswordEntityCoreData()
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +287,9 @@ public class PasswordStore {
|
||||||
if fm.fileExists(atPath: filePath, isDirectory: &isDirectory) {
|
if fm.fileExists(atPath: filePath, isDirectory: &isDirectory) {
|
||||||
if isDirectory.boolValue {
|
if isDirectory.boolValue {
|
||||||
e.isDir = true
|
e.isDir = true
|
||||||
let files = try fm.contentsOfDirectory(atPath: filePath).map { (filename) -> PasswordEntity in
|
let files = try fm.contentsOfDirectory(atPath: filePath).filter {
|
||||||
|
!$0.hasPrefix(".")
|
||||||
|
}.map { (filename) -> PasswordEntity in
|
||||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||||
if filename.hasSuffix(".gpg") {
|
if filename.hasSuffix(".gpg") {
|
||||||
passwordEntity.name = String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -4)))
|
passwordEntity.name = String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -4)))
|
||||||
|
|
@ -693,11 +697,12 @@ public class PasswordStore {
|
||||||
// get a list of local commits
|
// get a list of local commits
|
||||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
||||||
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||||
|
let 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, requestPGPKeyPassphrase: requestPGPKeyPassphrase) else {
|
guard let decryptedData = try PGPAgent.shared.decrypt(encryptedData: encryptedData, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase) else {
|
||||||
throw AppError.Decryption
|
throw AppError.Decryption
|
||||||
}
|
}
|
||||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||||
|
|
@ -706,7 +711,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encrypt(password: Password) throws -> Data {
|
public func encrypt(password: Password) throws -> Data {
|
||||||
return try PGPAgent.shared.encrypt(plainData: password.plainData)
|
return try PGPAgent.shared.encrypt(plainData: password.plainData, keyID: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeGitSSHKeys() {
|
public func removeGitSSHKeys() {
|
||||||
|
|
@ -718,3 +723,18 @@ public class PasswordStore {
|
||||||
gitSSHPrivateKeyPassphrase = nil
|
gitSSHPrivateKeyPassphrase = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func findGPGID(from url: URL) -> String {
|
||||||
|
var path = url
|
||||||
|
while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path)
|
||||||
|
&& path.path != "file:///" {
|
||||||
|
path = path.deletingLastPathComponent()
|
||||||
|
}
|
||||||
|
path = path.appendingPathComponent(".gpg-id")
|
||||||
|
|
||||||
|
do {
|
||||||
|
return try String(contentsOf: path).trimmed
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ class CryptoFrameworkTest: XCTestCase {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
XCTAssertNil(error)
|
XCTAssertNil(error)
|
||||||
|
|
||||||
XCTAssert(publicKey.getHexKeyID().hasSuffix(keyTriple.fingerprint))
|
XCTAssert(publicKey.getHexKeyID().hasSuffix(keyTriple.fingerprint))
|
||||||
XCTAssertNil(error)
|
XCTAssertNil(error)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,31 @@ class PGPAgentTest: XCTestCase {
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func basicEncryptDecrypt(using pgpAgent: PGPAgent, requestPassphrase: () -> String = requestPGPKeyPassphrase, encryptInArmored: Bool = true, encryptInArmoredNow: Bool = true) throws -> Data? {
|
func basicEncryptDecrypt(using pgpAgent: PGPAgent, keyID: String, requestPassphrase: () -> String = requestPGPKeyPassphrase, encryptInArmored: Bool = true, encryptInArmoredNow: Bool = true) throws -> Data? {
|
||||||
passKit.Defaults.encryptInArmored = encryptInArmored
|
passKit.Defaults.encryptInArmored = encryptInArmored
|
||||||
let encryptedData = try pgpAgent.encrypt(plainData: testData)
|
let encryptedData = try pgpAgent.encrypt(plainData: testData, keyID: keyID)
|
||||||
passKit.Defaults.encryptInArmored = encryptInArmoredNow
|
passKit.Defaults.encryptInArmored = encryptInArmoredNow
|
||||||
return try pgpAgent.decrypt(encryptedData: encryptedData, requestPGPKeyPassphrase: requestPassphrase)
|
return try pgpAgent.decrypt(encryptedData: encryptedData, keyID: keyID, requestPGPKeyPassphrase: requestPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMultiKeys() throws {
|
||||||
|
try [
|
||||||
|
RSA2048_RSA4096
|
||||||
|
].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()
|
||||||
|
try [
|
||||||
|
(true, true), (true, false), (false, true), (false, false)
|
||||||
|
].forEach{ a, b in
|
||||||
|
for id in keyTriple.fingerprint {
|
||||||
|
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: id, encryptInArmored: a, encryptInArmoredNow: b), testData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBasicEncryptDecrypt() throws {
|
func testBasicEncryptDecrypt() throws {
|
||||||
|
|
@ -57,7 +77,7 @@ class PGPAgentTest: XCTestCase {
|
||||||
try [
|
try [
|
||||||
(true, true), (true, false), (false, true), (false, false)
|
(true, true), (true, false), (false, true), (false, false)
|
||||||
].forEach{ a, b in
|
].forEach{ a, b in
|
||||||
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, encryptInArmored: a, encryptInArmoredNow: b), testData)
|
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: keyTriple.fingerprint, encryptInArmored: a, encryptInArmoredNow: b), testData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +88,7 @@ class PGPAgentTest: XCTestCase {
|
||||||
XCTAssertThrowsError(try pgpAgent.initKeys()) {
|
XCTAssertThrowsError(try pgpAgent.initKeys()) {
|
||||||
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
|
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
|
||||||
}
|
}
|
||||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
|
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint)) {
|
||||||
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
|
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +96,7 @@ class PGPAgentTest: XCTestCase {
|
||||||
func testInterchangePublicAndPrivateKey() throws {
|
func testInterchangePublicAndPrivateKey() throws {
|
||||||
try importKeys(RSA2048.privateKey, RSA2048.publicKey)
|
try importKeys(RSA2048.privateKey, RSA2048.publicKey)
|
||||||
XCTAssert(pgpAgent.isPrepared)
|
XCTAssert(pgpAgent.isPrepared)
|
||||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
|
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint)) {
|
||||||
XCTAssert($0.localizedDescription.contains("gopenpgp: unable to add locked key to a keyring"))
|
XCTAssert($0.localizedDescription.contains("gopenpgp: unable to add locked key to a keyring"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +104,7 @@ class PGPAgentTest: XCTestCase {
|
||||||
func testIncompatibleKeyTypes() throws {
|
func testIncompatibleKeyTypes() throws {
|
||||||
try importKeys(ED25519.publicKey, RSA2048.privateKey)
|
try importKeys(ED25519.publicKey, RSA2048.privateKey)
|
||||||
XCTAssert(pgpAgent.isPrepared)
|
XCTAssert(pgpAgent.isPrepared)
|
||||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
|
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint)) {
|
||||||
XCTAssertEqual($0 as! AppError, AppError.KeyExpiredOrIncompatible)
|
XCTAssertEqual($0 as! AppError, AppError.KeyExpiredOrIncompatible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +112,7 @@ class PGPAgentTest: XCTestCase {
|
||||||
func testCorruptedKey() throws {
|
func testCorruptedKey() throws {
|
||||||
try importKeys(RSA2048.publicKey.replacingOccurrences(of: "1", with: ""), RSA2048.privateKey)
|
try importKeys(RSA2048.publicKey.replacingOccurrences(of: "1", with: ""), RSA2048.privateKey)
|
||||||
XCTAssert(pgpAgent.isPrepared)
|
XCTAssert(pgpAgent.isPrepared)
|
||||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
|
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint)) {
|
||||||
XCTAssert($0.localizedDescription.contains("Can't read keys. Invalid input."))
|
XCTAssert($0.localizedDescription.contains("Can't read keys. Invalid input."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,10 +120,10 @@ class PGPAgentTest: XCTestCase {
|
||||||
func testUnsettKeys() throws {
|
func testUnsettKeys() throws {
|
||||||
try importKeys(ED25519.publicKey, ED25519.privateKey)
|
try importKeys(ED25519.publicKey, ED25519.privateKey)
|
||||||
XCTAssert(pgpAgent.isPrepared)
|
XCTAssert(pgpAgent.isPrepared)
|
||||||
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent), testData)
|
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: ED25519.fingerprint), testData)
|
||||||
keychain.removeContent(for: PgpKey.PUBLIC.getKeychainKey())
|
keychain.removeContent(for: PgpKey.PUBLIC.getKeychainKey())
|
||||||
keychain.removeContent(for: PgpKey.PRIVATE.getKeychainKey())
|
keychain.removeContent(for: PgpKey.PRIVATE.getKeychainKey())
|
||||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent)) {
|
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: ED25519.fingerprint)) {
|
||||||
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
|
XCTAssertEqual($0 as! AppError, AppError.KeyImport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,17 +142,17 @@ class PGPAgentTest: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide the correct passphrase.
|
// Provide the correct passphrase.
|
||||||
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, requestPassphrase: provideCorrectPassphrase), testData)
|
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint, requestPassphrase: provideCorrectPassphrase), testData)
|
||||||
XCTAssertEqual(passphraseRequestCalledCount, 1)
|
XCTAssertEqual(passphraseRequestCalledCount, 1)
|
||||||
|
|
||||||
// Provide the wrong passphrase.
|
// Provide the wrong passphrase.
|
||||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, requestPassphrase: provideIncorrectPassphrase)) {
|
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint, requestPassphrase: provideIncorrectPassphrase)) {
|
||||||
XCTAssertEqual($0 as! AppError, AppError.WrongPassphrase)
|
XCTAssertEqual($0 as! AppError, AppError.WrongPassphrase)
|
||||||
}
|
}
|
||||||
XCTAssertEqual(passphraseRequestCalledCount, 2)
|
XCTAssertEqual(passphraseRequestCalledCount, 2)
|
||||||
|
|
||||||
// Ask for the passphrase because the previous decryption has failed.
|
// Ask for the passphrase because the previous decryption has failed.
|
||||||
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, requestPassphrase: provideCorrectPassphrase), testData)
|
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint, requestPassphrase: provideCorrectPassphrase), testData)
|
||||||
XCTAssertEqual(passphraseRequestCalledCount, 3)
|
XCTAssertEqual(passphraseRequestCalledCount, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,49 @@ class PasswordStoreTest: XCTestCase {
|
||||||
}()
|
}()
|
||||||
let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")!
|
let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")!
|
||||||
|
|
||||||
func testClone() throws {
|
|
||||||
|
func testCloneAndDecryptMultiKeys() throws {
|
||||||
let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test")
|
let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test")
|
||||||
let passwordStore = PasswordStore(url: url)
|
let passwordStore = PasswordStore(url: url)
|
||||||
|
let expectation = self.expectation(description: "clone")
|
||||||
try passwordStore.cloneRepository(
|
try passwordStore.cloneRepository(
|
||||||
remoteRepoURL: remoteRepoURL,
|
remoteRepoURL: remoteRepoURL,
|
||||||
options: cloneOptions,
|
options: cloneOptions,
|
||||||
branchName: "master",
|
branchName: "master",
|
||||||
transferProgressBlock: { _, _ in },
|
transferProgressBlock: { _, _ in },
|
||||||
checkoutProgressBlock: { _, _, _ in }
|
checkoutProgressBlock: { _, _, _ in }
|
||||||
)
|
) {
|
||||||
|
expectation.fulfill()
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 3, handler: nil)
|
||||||
|
|
||||||
|
[
|
||||||
|
("work/github.com", "4712286271220DB299883EA7062E678DA1024DAE"),
|
||||||
|
("personal/github.com", "787EAE1A5FA3E749AA34CC6AA0645EBED862027E")
|
||||||
|
].forEach {(path, id) in
|
||||||
|
let keyID = findGPGID(from: url.appendingPathComponent(path))
|
||||||
|
XCTAssertEqual(keyID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let keychain = AppKeychain.shared
|
||||||
|
try KeyFileManager(keyType: PgpKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: RSA2048_RSA4096.publicKey)
|
||||||
|
try KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: RSA2048_RSA4096.privateKey)
|
||||||
|
try PGPAgent.shared.initKeys()
|
||||||
|
|
||||||
|
let personal = try decrypt(passwordStore: passwordStore, path: "personal/github.com.gpg", passphrase: "passforios")
|
||||||
|
XCTAssertEqual(personal.plainText, "passwordforpersonal\n")
|
||||||
|
|
||||||
|
let work = try decrypt(passwordStore: passwordStore, path: "work/github.com.gpg", passphrase: "passforios")
|
||||||
|
XCTAssertEqual(work.plainText, "passwordforwork\n")
|
||||||
|
|
||||||
passwordStore.erase()
|
passwordStore.erase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func decrypt(passwordStore: PasswordStore, path: String, passphrase: String) throws -> Password {
|
||||||
|
let entity = passwordStore.getPasswordEntity(by: path, isDir: false)!
|
||||||
|
return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: { passphrase } )!
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@ struct PGPKeyTestTriple {
|
||||||
let passphrase = "passforios"
|
let passphrase = "passforios"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MultiPGPKeyTestTriple {
|
||||||
|
let publicKey: String
|
||||||
|
let privateKey: String
|
||||||
|
let fingerprint: [String]
|
||||||
|
let passphrase: [String]
|
||||||
|
}
|
||||||
|
|
||||||
let RSA2048 = PGPKeyTestTriple(
|
let RSA2048 = PGPKeyTestTriple(
|
||||||
publicKey: PGP_RSA2048_PUBLIC_KEY,
|
publicKey: PGP_RSA2048_PUBLIC_KEY,
|
||||||
privateKey: PGP_RSA2048_PRIVATE_KEY,
|
privateKey: PGP_RSA2048_PRIVATE_KEY,
|
||||||
|
|
@ -54,6 +61,13 @@ let ED25519_SUB = PGPKeyTestTriple(
|
||||||
fingerprint: "e9444483"
|
fingerprint: "e9444483"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let RSA2048_RSA4096 = MultiPGPKeyTestTriple(
|
||||||
|
publicKey: PGP_RSA2048_PUBLIC_KEY + "\n" + PGP_RSA4096_PUBLIC_KEY,
|
||||||
|
privateKey: PGP_RSA2048_PRIVATE_KEY + "\n" + PGP_RSA4096_PRIVATE_KEY,
|
||||||
|
fingerprint: ["a1024dae", "d862027e"],
|
||||||
|
passphrase: ["passforios", "passforios"]
|
||||||
|
)
|
||||||
|
|
||||||
func requestPGPKeyPassphrase() -> String {
|
func requestPGPKeyPassphrase() -> String {
|
||||||
return "passforios"
|
return "passforios"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue