PGPInterface can encrypt with multiple keys, PGPAgent can encrypt with all keys
This commit is contained in:
parent
8d4f3af475
commit
84eaf4ad7d
7 changed files with 158 additions and 19 deletions
|
|
@ -180,6 +180,35 @@ final class PGPAgentTest: XCTestCase {
|
|||
XCTAssertEqual(passphraseRequestCalledCount, 3)
|
||||
}
|
||||
|
||||
func testEncryptWithAllKeys() throws {
|
||||
// When multiple keys are imported, the agent should be able to encrypt without specifying the keyID.
|
||||
// It should use all public keys for which we also have private keys, and the encrypted message should be able to be decrypted by any of the private keys.
|
||||
|
||||
keychain.removeAllContent()
|
||||
// no private key for ED25519
|
||||
try importKeys(RSA2048_RSA4096.publicKeys | ED25519.publicKey, RSA2048_RSA4096.privateKeys)
|
||||
try pgpAgent.initKeys()
|
||||
|
||||
let encryptedData = try pgpAgent.encryptWithAllKeys(plainData: testData)
|
||||
|
||||
try [RSA2048.fingerprint, RSA4096.fingerprint].forEach { keyID in
|
||||
let decryptedData = try pgpAgent.decrypt(encryptedData: encryptedData, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||
XCTAssertEqual(decryptedData, testData)
|
||||
}
|
||||
|
||||
XCTAssertThrowsError(try pgpAgent.decrypt(encryptedData: encryptedData, keyID: ED25519.fingerprint, requestPGPKeyPassphrase: requestPGPKeyPassphrase)) {
|
||||
XCTAssertEqual($0 as! AppError, AppError.pgpPrivateKeyNotFound(keyID: ED25519.fingerprint))
|
||||
}
|
||||
|
||||
// add private key for ED25519
|
||||
try importKeys(RSA2048_RSA4096.publicKeys | ED25519.publicKey, RSA2048_RSA4096.privateKeys | ED25519.privateKey)
|
||||
try pgpAgent.initKeys()
|
||||
|
||||
XCTAssertThrowsError(try pgpAgent.decrypt(encryptedData: encryptedData, keyID: ED25519.fingerprint, requestPGPKeyPassphrase: requestPGPKeyPassphrase)) {
|
||||
XCTAssertEqual($0 as! AppError, AppError.keyExpiredOrIncompatible)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -536,12 +536,13 @@ final class PGPAgentLowLevelTests: XCTestCase {
|
|||
_ = try agent.encrypt(plainData: testDecryptedData, keyID: shortID)
|
||||
|
||||
XCTAssertEqual(mockPGP.containsPublicKeyCalls, [shortID])
|
||||
XCTAssertEqual(mockPGP.encryptCalls[0].keyID, shortID)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls.count, 1)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls[0].keyIDs, [shortID])
|
||||
}
|
||||
|
||||
// MARK: - Encrypt passthrough tests (for completeness of mock interaction)
|
||||
|
||||
/// encrypt(plainData:keyID:) calls containsPublicKey and passes data through.
|
||||
/// encrypt(plainData:keyID:) calls containsPublicKey and passes data through via encrypt(plainData:keyIDs:).
|
||||
func testEncryptWithKeyID_keyFound_callsInterface() throws {
|
||||
let longFingerprint = "4712286271220db299883ea7062e678da1024dae"
|
||||
mockPGP.publicKeyIDs = [longFingerprint]
|
||||
|
|
@ -549,9 +550,9 @@ final class PGPAgentLowLevelTests: XCTestCase {
|
|||
let result = try agent.encrypt(plainData: testDecryptedData, keyID: longFingerprint)
|
||||
|
||||
XCTAssertEqual(result, mockPGP.encryptResult)
|
||||
XCTAssertEqual(mockPGP.encryptCalls.count, 1)
|
||||
XCTAssertEqual(mockPGP.encryptCalls[0].keyID, longFingerprint)
|
||||
XCTAssertEqual(mockPGP.encryptCalls[0].plainData, testDecryptedData)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls.count, 1)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls[0].keyIDs, [longFingerprint])
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls[0].plainData, testDecryptedData)
|
||||
}
|
||||
|
||||
/// encrypt with unknown key and single available key falls back.
|
||||
|
|
@ -565,7 +566,7 @@ final class PGPAgentLowLevelTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(result, mockPGP.encryptResult)
|
||||
XCTAssertEqual(mockPGP.containsPublicKeyCalls, [shortID])
|
||||
XCTAssertEqual(mockPGP.encryptCalls[0].keyID, longFingerprint)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls[0].keyIDs, [longFingerprint])
|
||||
}
|
||||
|
||||
/// encrypt with unknown key and multiple keys throws.
|
||||
|
|
@ -576,10 +577,10 @@ final class PGPAgentLowLevelTests: XCTestCase {
|
|||
XCTAssertThrowsError(try agent.encrypt(plainData: testDecryptedData, keyID: "a1024dae")) { error in
|
||||
XCTAssertEqual(error as? AppError, AppError.pgpPublicKeyNotFound(keyID: "a1024dae"))
|
||||
}
|
||||
XCTAssertEqual(mockPGP.encryptCalls.count, 0)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls.count, 0)
|
||||
}
|
||||
|
||||
/// encrypt(plainData:) without keyID passes nil to interface.
|
||||
/// encrypt(plainData:) without keyID passes nil to the deprecated interface method.
|
||||
func testEncryptNoKeyID_passesNilToInterface() throws {
|
||||
let result = try agent.encrypt(plainData: testDecryptedData)
|
||||
|
||||
|
|
@ -599,4 +600,39 @@ final class PGPAgentLowLevelTests: XCTestCase {
|
|||
XCTAssertEqual(error as? AppError, AppError.encryption)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - encryptWithAllKeys
|
||||
|
||||
/// encryptWithAllKeys delegates to pgpInterface.encryptWithAllKeys.
|
||||
func testEncryptWithAllKeys_callsInterface() throws {
|
||||
mockPGP.encryptResult = Data("all-keys-encrypted".utf8)
|
||||
|
||||
let result = try agent.encryptWithAllKeys(plainData: testDecryptedData)
|
||||
|
||||
XCTAssertEqual(result, Data("all-keys-encrypted".utf8))
|
||||
XCTAssertEqual(mockPGP.encryptWithAllKeysCalls.count, 1)
|
||||
XCTAssertEqual(mockPGP.encryptWithAllKeysCalls[0].plainData, testDecryptedData)
|
||||
// Does not call containsPublicKey or the single/multi-key encrypt methods.
|
||||
XCTAssertEqual(mockPGP.containsPublicKeyCalls.count, 0)
|
||||
XCTAssertEqual(mockPGP.encryptCalls.count, 0)
|
||||
XCTAssertEqual(mockPGP.encryptMultiKeyCalls.count, 0)
|
||||
}
|
||||
|
||||
/// encryptWithAllKeys propagates errors from interface.
|
||||
func testEncryptWithAllKeys_interfaceThrows_propagatesError() {
|
||||
mockPGP.encryptError = AppError.encryption
|
||||
|
||||
XCTAssertThrowsError(try agent.encryptWithAllKeys(plainData: testDecryptedData)) { error in
|
||||
XCTAssertEqual(error as? AppError, AppError.encryption)
|
||||
}
|
||||
}
|
||||
|
||||
/// encryptWithAllKeys throws encryption error when pgpInterface is nil (checkAndInit fails).
|
||||
func testEncryptWithAllKeys_checkAndInit_requiresPGPKeyPassphraseInKeystore() throws {
|
||||
keychain.removeContent(for: Globals.pgpKeyPassphrase)
|
||||
|
||||
XCTAssertThrowsError(try agent.encryptWithAllKeys(plainData: testDecryptedData)) { error in
|
||||
XCTAssertEqual(error as? AppError, AppError.keyImport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,20 @@ class MockPGPInterface: PGPInterface {
|
|||
let keyID: String?
|
||||
}
|
||||
|
||||
struct EncryptMultiKeyCall {
|
||||
let plainData: Data
|
||||
let keyIDs: [String]
|
||||
}
|
||||
|
||||
struct EncryptWithAllKeysCall {
|
||||
let plainData: Data
|
||||
}
|
||||
|
||||
var decryptCalls: [DecryptCall] = []
|
||||
var resolvedPassphrases: [String] = []
|
||||
var encryptCalls: [EncryptCall] = []
|
||||
var encryptMultiKeyCalls: [EncryptMultiKeyCall] = []
|
||||
var encryptWithAllKeysCalls: [EncryptWithAllKeysCall] = []
|
||||
var containsPublicKeyCalls: [String] = []
|
||||
var containsPrivateKeyCalls: [String] = []
|
||||
|
||||
|
|
@ -63,6 +74,22 @@ class MockPGPInterface: PGPInterface {
|
|||
return encryptResult
|
||||
}
|
||||
|
||||
func encryptWithAllKeys(plainData: Data) throws -> Data {
|
||||
encryptWithAllKeysCalls.append(EncryptWithAllKeysCall(plainData: plainData))
|
||||
if let error = encryptError {
|
||||
throw error
|
||||
}
|
||||
return encryptResult
|
||||
}
|
||||
|
||||
func encrypt(plainData: Data, keyIDs: [String]) throws -> Data {
|
||||
encryptMultiKeyCalls.append(EncryptMultiKeyCall(plainData: plainData, keyIDs: keyIDs))
|
||||
if let error = encryptError {
|
||||
throw error
|
||||
}
|
||||
return encryptResult
|
||||
}
|
||||
|
||||
func containsPublicKey(with keyID: String) -> Bool {
|
||||
containsPublicKeyCalls.append(keyID)
|
||||
return publicKeyIDs.contains { $0.hasSuffix(keyID.lowercased()) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue