2019-09-08 23:00:46 +02:00
|
|
|
//
|
2020-04-19 15:41:30 +02:00
|
|
|
// GopenPGPInterface.swift
|
2019-09-08 23:00:46 +02:00
|
|
|
// passKit
|
|
|
|
|
//
|
|
|
|
|
// Created by Danny Moesch on 08.09.19.
|
|
|
|
|
// Copyright © 2019 Bob Sun. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Crypto
|
|
|
|
|
|
2020-04-19 15:41:30 +02:00
|
|
|
struct GopenPGPInterface: PGPInterface {
|
2019-10-20 12:14:51 +02:00
|
|
|
private static let errorMapping: [String: Error] = [
|
2020-09-20 15:07:18 +02:00
|
|
|
"gopenpgp: error in unlocking key: openpgp: invalid data: private key checksum failure": AppError.wrongPassphrase,
|
|
|
|
|
"openpgp: incorrect key": AppError.keyExpiredOrIncompatible,
|
2019-10-20 12:14:51 +02:00
|
|
|
]
|
|
|
|
|
|
2020-04-13 01:30:00 -07:00
|
|
|
private var publicKeys: [String: CryptoKey] = [:]
|
|
|
|
|
private var privateKeys: [String: CryptoKey] = [:]
|
2019-09-08 23:00:46 +02:00
|
|
|
|
|
|
|
|
init(publicArmoredKey: String, privateArmoredKey: String) throws {
|
2020-04-13 01:30:00 -07:00
|
|
|
let pubKeys = extractKeysFromArmored(str: publicArmoredKey)
|
|
|
|
|
let prvKeys = extractKeysFromArmored(str: privateArmoredKey)
|
|
|
|
|
|
|
|
|
|
for key in pubKeys {
|
|
|
|
|
var error: NSError?
|
2020-09-20 15:07:18 +02:00
|
|
|
guard let cryptoKey = CryptoNewKeyFromArmored(key, &error) else {
|
2020-04-13 01:30:00 -07:00
|
|
|
guard error == nil else {
|
|
|
|
|
throw error!
|
|
|
|
|
}
|
2020-09-20 15:07:18 +02:00
|
|
|
throw AppError.keyImport
|
2020-04-13 01:30:00 -07:00
|
|
|
}
|
2020-09-20 15:07:18 +02:00
|
|
|
publicKeys[cryptoKey.getFingerprint().lowercased()] = cryptoKey
|
2020-04-13 01:30:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for key in prvKeys {
|
|
|
|
|
var error: NSError?
|
2020-09-20 15:07:18 +02:00
|
|
|
guard let cryptoKey = CryptoNewKeyFromArmored(key, &error) else {
|
2020-04-13 01:30:00 -07:00
|
|
|
guard error == nil else {
|
|
|
|
|
throw error!
|
|
|
|
|
}
|
2020-09-20 15:07:18 +02:00
|
|
|
throw AppError.keyImport
|
2020-04-11 23:23:38 -07:00
|
|
|
}
|
2020-09-20 15:07:18 +02:00
|
|
|
privateKeys[cryptoKey.getFingerprint().lowercased()] = cryptoKey
|
2019-09-08 23:00:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 01:30:00 -07:00
|
|
|
func extractKeysFromArmored(str: String) -> [String] {
|
2020-06-28 21:25:40 +02:00
|
|
|
var keys: [String] = []
|
|
|
|
|
var key: String = ""
|
|
|
|
|
for line in str.splitByNewline() {
|
|
|
|
|
if line.trimmed.uppercased().hasPrefix("-----BEGIN PGP") {
|
|
|
|
|
key = ""
|
|
|
|
|
key += line
|
|
|
|
|
} else if line.trimmed.uppercased().hasPrefix("-----END PGP") {
|
|
|
|
|
key += line
|
|
|
|
|
keys.append(key)
|
|
|
|
|
} else {
|
|
|
|
|
key += line
|
|
|
|
|
}
|
2020-04-13 19:15:52 -07:00
|
|
|
key += "\n"
|
2020-06-28 21:25:40 +02:00
|
|
|
}
|
|
|
|
|
return keys
|
|
|
|
|
}
|
2020-04-13 01:30:00 -07:00
|
|
|
|
2020-04-14 20:20:16 -07:00
|
|
|
func containsPublicKey(with keyID: String) -> Bool {
|
2020-07-05 23:15:29 +02:00
|
|
|
publicKeys.keys.contains { key in key.hasSuffix(keyID.lowercased()) }
|
2020-04-14 20:20:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func containsPrivateKey(with keyID: String) -> Bool {
|
2020-07-05 23:15:29 +02:00
|
|
|
privateKeys.keys.contains { key in key.hasSuffix(keyID.lowercased()) }
|
2020-04-14 20:20:16 -07:00
|
|
|
}
|
|
|
|
|
|
2021-01-07 21:58:38 -08:00
|
|
|
func decrypt(encryptedData: Data, keyID: String?, passphrase: String) throws -> Data? {
|
|
|
|
|
let key: CryptoKey? = {
|
|
|
|
|
if let keyID = keyID {
|
|
|
|
|
return privateKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) })?.value
|
|
|
|
|
}
|
2021-01-31 13:34:37 +01:00
|
|
|
return privateKeys.first?.value
|
2021-01-07 21:58:38 -08:00
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
guard let privateKey = key else {
|
2020-09-20 15:07:18 +02:00
|
|
|
throw AppError.decryption
|
2020-04-13 01:30:00 -07:00
|
|
|
}
|
|
|
|
|
|
2019-10-20 12:14:51 +02:00
|
|
|
do {
|
2021-01-10 15:01:21 -08:00
|
|
|
var isLocked: ObjCBool = false
|
|
|
|
|
try privateKey.isLocked(&isLocked)
|
|
|
|
|
var unlockedKey: CryptoKey!
|
|
|
|
|
if isLocked.boolValue {
|
|
|
|
|
unlockedKey = try privateKey.unlock(passphrase.data(using: .utf8))
|
|
|
|
|
} else {
|
|
|
|
|
unlockedKey = privateKey
|
|
|
|
|
}
|
2020-04-11 23:23:38 -07:00
|
|
|
var error: NSError?
|
|
|
|
|
|
|
|
|
|
guard let keyRing = CryptoNewKeyRing(unlockedKey, &error) else {
|
|
|
|
|
guard error == nil else {
|
|
|
|
|
throw error!
|
|
|
|
|
}
|
2020-09-20 15:07:18 +02:00
|
|
|
throw AppError.decryption
|
2020-04-11 23:23:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let message = createPgpMessage(from: encryptedData)
|
|
|
|
|
return try keyRing.decrypt(message, verifyKey: nil, verifyTime: 0).data
|
2019-10-20 12:14:51 +02:00
|
|
|
} catch {
|
|
|
|
|
throw Self.errorMapping[error.localizedDescription, default: error]
|
|
|
|
|
}
|
2019-09-08 23:00:46 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-07 21:58:38 -08:00
|
|
|
func encrypt(plainData: Data, keyID: String?) throws -> Data {
|
|
|
|
|
let key: CryptoKey? = {
|
|
|
|
|
if let keyID = keyID {
|
|
|
|
|
return publicKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) })?.value
|
|
|
|
|
}
|
2021-01-31 13:34:37 +01:00
|
|
|
return publicKeys.first?.value
|
2021-01-07 21:58:38 -08:00
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
guard let publicKey = key else {
|
2020-09-20 15:07:18 +02:00
|
|
|
throw AppError.encryption
|
2020-04-13 01:30:00 -07:00
|
|
|
}
|
|
|
|
|
|
2020-04-11 23:23:38 -07:00
|
|
|
var error: NSError?
|
|
|
|
|
|
|
|
|
|
guard let keyRing = CryptoNewKeyRing(publicKey, &error) else {
|
|
|
|
|
guard error == nil else {
|
|
|
|
|
throw error!
|
|
|
|
|
}
|
2020-09-20 15:07:18 +02:00
|
|
|
throw AppError.encryption
|
2020-04-11 23:23:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let encryptedData = try keyRing.encrypt(CryptoNewPlainMessage(plainData.mutable as Data), privateKey: nil)
|
2020-01-02 00:48:00 +01:00
|
|
|
if Defaults.encryptInArmored {
|
2019-09-08 23:00:46 +02:00
|
|
|
var error: NSError?
|
|
|
|
|
let armor = encryptedData.getArmored(&error)
|
|
|
|
|
guard error == nil else {
|
|
|
|
|
throw error!
|
|
|
|
|
}
|
|
|
|
|
return armor.data(using: .ascii)!
|
|
|
|
|
}
|
|
|
|
|
return encryptedData.getBinary()!
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 19:15:52 -07:00
|
|
|
var keyID: [String] {
|
2020-06-28 21:25:40 +02:00
|
|
|
publicKeys.keys.map { $0.uppercased() }
|
2020-04-11 23:23:38 -07:00
|
|
|
}
|
|
|
|
|
|
2020-04-13 19:15:52 -07:00
|
|
|
var shortKeyID: [String] {
|
2020-06-28 21:25:40 +02:00
|
|
|
publicKeys.keys.map { $0.suffix(8).uppercased() }
|
2019-09-08 23:00:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func createPgpMessage(from encryptedData: Data) -> CryptoPGPMessage? {
|
2019-09-30 00:12:54 +08:00
|
|
|
// Important note:
|
2020-01-02 00:48:00 +01:00
|
|
|
// Even if Defaults.encryptInArmored is true now, it could be different during the encryption.
|
2019-09-30 00:12:54 +08:00
|
|
|
var error: NSError?
|
|
|
|
|
let message = CryptoNewPGPMessageFromArmored(String(data: encryptedData, encoding: .ascii), &error)
|
|
|
|
|
if error == nil {
|
|
|
|
|
return message
|
2019-09-08 23:00:46 +02:00
|
|
|
}
|
|
|
|
|
return CryptoNewPGPMessage(encryptedData.mutable as Data)
|
|
|
|
|
}
|
|
|
|
|
}
|