passforios/passKit/Models/PGPAgent.swift
Danny Moesch 5c7d4e55a4 Introduce KeyStore protocol in order to provide specialized key store implementations for tests
With the DictBasedKeychain the main AppKeychain is not influenced by tests. The previous implementation led to an empty Keychain requiring a new setup of the simulator.
2019-07-20 23:42:32 +02:00

185 lines
5.9 KiB
Swift

//
// 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 Gopenpgpwrapper
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: GopenpgpwrapperKey? {
didSet {
pgpKeyID = publicKey?.getID()
}
}
private var privateKey: GopenpgpwrapperKey?
// 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 GopenpgpwrapperReadKey first.
if let key = GopenpgpwrapperReadKey(pgpKeyData) {
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 {
if let decryptedData = privateKey?.decrypt(encryptedData, passphrase: passphrase) {
return decryptedData
}
}
// 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 = publicKey?.encrypt(plainData, armor: SharedDefaults[.encryptInArmored]) {
return encryptedData
}
}
// 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()
}
}