PGPInterface can encrypt with multiple keys, PGPAgent can encrypt with all keys
This commit is contained in:
parent
39dab8c6c0
commit
a56193dc86
5 changed files with 87 additions and 11 deletions
|
|
@ -123,26 +123,43 @@ struct GopenPGPInterface: PGPInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Use encrypt(plainData:keyIDs:) instead.")
|
||||||
func encrypt(plainData: Data, keyID: String?) throws -> Data {
|
func encrypt(plainData: Data, keyID: String?) throws -> Data {
|
||||||
let key: CryptoKey? = {
|
guard let keyID = keyID ?? publicKeys.keys.first else {
|
||||||
if let keyID {
|
// this is invalid, but we want the new function to throw the error for us
|
||||||
return publicKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) })?.value
|
return try encrypt(plainData: plainData, keyIDs: [])
|
||||||
|
}
|
||||||
|
return try encrypt(plainData: plainData, keyIDs: [keyID])
|
||||||
}
|
}
|
||||||
return publicKeys.first?.value
|
|
||||||
}()
|
|
||||||
|
|
||||||
guard let publicKey = key else {
|
func encryptWithAllKeys(plainData: Data) throws -> Data {
|
||||||
|
let keyIDs = publicKeys.keys.filter { key in privateKeys.keys.contains(key) }
|
||||||
|
return try encrypt(plainData: plainData, keyIDs: keyIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(plainData: Data, keyIDs: [String]) throws -> Data {
|
||||||
|
let keys: [CryptoKey] = keyIDs.compactMap { keyID in
|
||||||
|
publicKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) })?.value
|
||||||
|
}
|
||||||
|
guard let firstKey = keys.first else {
|
||||||
throw AppError.encryption
|
throw AppError.encryption
|
||||||
}
|
}
|
||||||
|
let otherKeys = keys.dropFirst()
|
||||||
|
|
||||||
var error: NSError?
|
var error: NSError?
|
||||||
|
guard let keyRing = CryptoNewKeyRing(firstKey, &error) else {
|
||||||
guard let keyRing = CryptoNewKeyRing(publicKey, &error) else {
|
|
||||||
guard error == nil else {
|
guard error == nil else {
|
||||||
throw error!
|
throw error!
|
||||||
}
|
}
|
||||||
throw AppError.encryption
|
throw AppError.encryption
|
||||||
}
|
}
|
||||||
|
do {
|
||||||
|
try otherKeys.forEach { key in
|
||||||
|
try keyRing.add(key)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
throw AppError.encryption
|
||||||
|
}
|
||||||
|
|
||||||
let encryptedData = try keyRing.encrypt(CryptoNewPlainMessage(plainData.mutable as Data), privateKey: nil)
|
let encryptedData = try keyRing.encrypt(CryptoNewPlainMessage(plainData.mutable as Data), privateKey: nil)
|
||||||
if Defaults.encryptInArmored {
|
if Defaults.encryptInArmored {
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,25 @@ struct ObjectivePGPInterface: PGPInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Use encrypt(plainData:keyIDs:) instead.")
|
||||||
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)
|
// Backwards compatibility: ignore keyID parameter and encrypted with all keys in the keyring
|
||||||
|
try encryptWithAllKeys(plainData: plainData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptWithAllKeys(plainData: Data) throws -> Data {
|
||||||
|
try encrypt(plainData: plainData, keyIDs: keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encrypt(plainData: Data, keyIDs: [String]) throws -> Data {
|
||||||
|
let keys = try keyIDs.map { keyID in
|
||||||
|
guard let key = keyring.findKey(keyID) else {
|
||||||
|
throw AppError.pgpPublicKeyNotFound(keyID: keyID)
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedData = try ObjectivePGP.encrypt(plainData, addSignature: false, using: 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)!
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class PGPAgent {
|
||||||
throw AppError.pgpPublicKeyNotFound(keyID: keyID)
|
throw AppError.pgpPublicKeyNotFound(keyID: keyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return try pgpInterface.encrypt(plainData: plainData, keyID: keyID)
|
return try pgpInterface.encrypt(plainData: plainData, keyIDs: [keyID])
|
||||||
}
|
}
|
||||||
|
|
||||||
public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Data? {
|
public func decrypt(encryptedData: Data, requestPGPKeyPassphrase: @escaping (String) -> String) throws -> Data? {
|
||||||
|
|
@ -122,6 +122,7 @@ public class PGPAgent {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Use encrypt(plainData:keyID:) or encryptWithAllKeys(plainData:) instead.")
|
||||||
public func encrypt(plainData: Data) throws -> Data {
|
public func encrypt(plainData: Data) throws -> Data {
|
||||||
try checkAndInit()
|
try checkAndInit()
|
||||||
guard let pgpInterface else {
|
guard let pgpInterface else {
|
||||||
|
|
@ -130,6 +131,14 @@ public class PGPAgent {
|
||||||
return try pgpInterface.encrypt(plainData: plainData, keyID: nil)
|
return try pgpInterface.encrypt(plainData: plainData, keyID: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func encryptWithAllKeys(plainData: Data) throws -> Data {
|
||||||
|
try checkAndInit()
|
||||||
|
guard let pgpInterface else {
|
||||||
|
throw AppError.encryption
|
||||||
|
}
|
||||||
|
return try pgpInterface.encryptWithAllKeys(plainData: plainData)
|
||||||
|
}
|
||||||
|
|
||||||
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())
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,11 @@
|
||||||
protocol PGPInterface {
|
protocol PGPInterface {
|
||||||
func decrypt(encryptedData: Data, keyIDHint: String?, passPhraseForKey: @escaping (String) -> String) throws -> Data?
|
func decrypt(encryptedData: Data, keyIDHint: String?, passPhraseForKey: @escaping (String) -> String) throws -> Data?
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Use encrypt(plainData:keyIDs:) instead.")
|
||||||
func encrypt(plainData: Data, keyID: String?) throws -> Data
|
func encrypt(plainData: Data, keyID: String?) throws -> Data
|
||||||
|
// encrypt with all public keys for which we also have a private key
|
||||||
|
func encryptWithAllKeys(plainData: Data) throws -> Data
|
||||||
|
func encrypt(plainData: Data, keyIDs: [String]) throws -> Data
|
||||||
|
|
||||||
func containsPublicKey(with keyID: String) -> Bool
|
func containsPublicKey(with keyID: String) -> Bool
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,35 @@ final class PGPAgentTest: XCTestCase {
|
||||||
XCTAssertEqual(passphraseRequestCalledCount, 3)
|
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 {
|
private func importKeys(_ publicKey: String, _ privateKey: String) throws {
|
||||||
try KeyFileManager(keyType: PGPKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: publicKey)
|
try KeyFileManager(keyType: PGPKey.PUBLIC, keyPath: "", keyHandler: keychain.add).importKey(from: publicKey)
|
||||||
try KeyFileManager(keyType: PGPKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: privateKey)
|
try KeyFileManager(keyType: PGPKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: privateKey)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue