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,
|
||||
]
|
||||
|
||||
private let publicKey: CryptoKey
|
||||
private let privateKey: CryptoKey
|
||||
private var publicKeys: [String: CryptoKey] = [:]
|
||||
private var privateKeys: [String: CryptoKey] = [:]
|
||||
|
||||
init(publicArmoredKey: String, privateArmoredKey: String) throws {
|
||||
var error: NSError?
|
||||
guard let publicKey = CryptoNewKeyFromArmored(publicArmoredKey, &error),
|
||||
let privateKey = CryptoNewKeyFromArmored(privateArmoredKey, &error) else {
|
||||
guard error == nil else {
|
||||
throw error!
|
||||
let pubKeys = extractKeysFromArmored(str: publicArmoredKey)
|
||||
let prvKeys = extractKeysFromArmored(str: privateArmoredKey)
|
||||
|
||||
for key in pubKeys {
|
||||
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? {
|
||||
guard let e = privateKeys.first(where: { (key, _) in key.hasSuffix(keyID.lowercased()) }),
|
||||
let privateKey = privateKeys[e.key] else {
|
||||
throw AppError.Decryption
|
||||
}
|
||||
|
||||
do {
|
||||
let unlockedKey = try privateKey.unlock(passphrase.data(using: .utf8))
|
||||
var error: NSError?
|
||||
|
|
@ -51,6 +88,11 @@ struct GopenPgp: PgpInterface {
|
|||
}
|
||||
|
||||
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?
|
||||
|
||||
guard let keyRing = CryptoNewKeyRing(publicKey, &error) else {
|
||||
|
|
@ -73,14 +115,12 @@ struct GopenPgp: PgpInterface {
|
|||
}
|
||||
|
||||
var keyId: String {
|
||||
var error: NSError?
|
||||
let fingerprint = publicKey.getHexKeyID()
|
||||
let fingerprint = publicKeys.first?.key ?? ""
|
||||
return String(fingerprint).uppercased()
|
||||
}
|
||||
|
||||
var shortKeyId: String {
|
||||
var error: NSError?
|
||||
let fingerprint = publicKey.getHexKeyID()
|
||||
let fingerprint = publicKeys.first?.key ?? ""
|
||||
return String(fingerprint.suffix(8)).uppercased()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class PGPAgent {
|
|||
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
|
||||
let previousDecryptStatus = self.latestDecryptStatus
|
||||
self.latestDecryptStatus = false
|
||||
|
|
@ -59,7 +59,7 @@ public class PGPAgent {
|
|||
passphrase = keyStore.get(for: Globals.pgpKeyPassphrase) ?? requestPGPKeyPassphrase()
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// The decryption step has succeed.
|
||||
|
|
@ -67,12 +67,12 @@ public class PGPAgent {
|
|||
return result
|
||||
}
|
||||
|
||||
public func encrypt(plainData: Data) throws -> Data {
|
||||
public func encrypt(plainData: Data, keyID: String) throws -> Data {
|
||||
try checkAndInit()
|
||||
guard let pgpInterface = pgpInterface else {
|
||||
throw AppError.Encryption
|
||||
}
|
||||
return try pgpInterface.encrypt(plainData: plainData, keyID: "")
|
||||
return try pgpInterface.encrypt(plainData: plainData, keyID: keyID)
|
||||
}
|
||||
|
||||
public var isPrepared: Bool {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,8 @@ public class PasswordStore {
|
|||
options: [AnyHashable : Any]? = nil,
|
||||
branchName: String,
|
||||
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: tempStoreURL)
|
||||
self.gitPassword = nil
|
||||
|
|
@ -221,6 +222,7 @@ public class PasswordStore {
|
|||
DispatchQueue.main.async {
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +287,9 @@ public class PasswordStore {
|
|||
if fm.fileExists(atPath: filePath, isDirectory: &isDirectory) {
|
||||
if isDirectory.boolValue {
|
||||
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
|
||||
if filename.hasSuffix(".gpg") {
|
||||
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
|
||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
||||
}
|
||||
|
||||
|
||||
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
||||
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||
let keyID = findGPGID(from: 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
|
||||
}
|
||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
|
|
@ -706,7 +711,7 @@ public class PasswordStore {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
|
@ -718,3 +723,18 @@ public class PasswordStore {
|
|||
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 ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue