Initial implementation of using YubiKey for decryption (#533)

This commit is contained in:
Mingshen Sun 2022-01-09 21:38:39 -08:00 committed by GitHub
parent 13804b79e6
commit 955e50c3d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 606 additions and 118 deletions

View file

@ -6,6 +6,8 @@
// Copyright © 2017 Bob Sun. All rights reserved.
//
import Foundation
public enum AppError: Error, Equatable {
case repositoryNotSet
case repositoryRemoteBranchNotFound(branchName: String)
@ -18,13 +20,31 @@ public enum AppError: Error, Equatable {
case gitPushNotSuccessful
case pgpPublicKeyNotFound(keyID: String)
case pgpPrivateKeyNotFound(keyID: String)
case yubiKey(YubiKeyError)
case passwordFileNotFound(path: String)
case keyExpiredOrIncompatible
case wrongPassphrase
case wrongPasswordFilename
case decryption
case encryption
case encoding
case unknown
case other(message: String)
}
public enum YubiKeyError: Error, Equatable {
case connection(message: String)
case selectApplication(message: String)
case verify(message: String)
case decipher(message: String)
}
extension YubiKeyError: LocalizedError {
public var errorDescription: String? {
switch self {
case let .connection(message), let .decipher(message), let .selectApplication(message), let .verify(message):
return message
}
}
}
extension AppError: LocalizedError {
@ -36,6 +56,8 @@ extension AppError: LocalizedError {
return localizationKey.localize(name)
case let .pgpPrivateKeyNotFound(keyID), let .pgpPublicKeyNotFound(keyID):
return localizationKey.localize(keyID)
case let .other(message):
return message.localize()
default:
return localizationKey.localize()
}

View file

@ -26,6 +26,7 @@ public extension DefaultsKeys {
var pgpKeySource: DefaultsKey<KeySource?> { .init("pgpKeySource") }
var pgpPublicKeyURL: DefaultsKey<URL?> { .init("pgpPublicKeyURL") }
var pgpPrivateKeyURL: DefaultsKey<URL?> { .init("pgpPrivateKeyURL") }
var isYubiKeyEnabled: DefaultsKey<Bool> { .init("isYubiKeyEnabled", defaultValue: false) }
// Keep them for legacy reasons.
var pgpPublicKeyArmor: DefaultsKey<String?> { .init("pgpPublicKeyArmor") }

View file

@ -0,0 +1,54 @@
//
// YubiKeyAPDU.swift
// passKit
//
// Copyright © 2022 Bob Sun. All rights reserved.
//
import YubiKit
public enum YubiKeyAPDU {
public static func selectOpenPGPApplication() -> YKFSelectApplicationAPDU {
let selectOpenPGPAPDU = YKFSelectApplicationAPDU(data: Data([0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]))!
return selectOpenPGPAPDU
}
public static func verify(password: String) -> YKFAPDU {
let pw1: [UInt8] = Array(password.utf8)
var apdu: [UInt8] = []
apdu += [0x00] // CLA
apdu += [0x20] // INS: VERIFY
apdu += [0x00] // P1
apdu += [0x82] // P2: PW1
apdu += withUnsafeBytes(of: UInt8(pw1.count).bigEndian, Array.init)
apdu += pw1
let verifyApdu = YKFAPDU(data: Data(apdu))!
return verifyApdu
}
public static func decipher(data: Data) -> YKFAPDU {
var apdu: [UInt8] = []
apdu += [0x00] // CLA
apdu += [0x2A, 0x80, 0x86] // INS, P1, P2: PSO.DECIPHER
// Lc, An extended Lc field consists of three bytes:
// one byte set to '00' followed by two bytes not set to '0000' (1 to 65535 dec.).
apdu += [0x00] + withUnsafeBytes(of: UInt16(data.count + 1).bigEndian, Array.init)
// Padding indicator byte (00) for RSA or (02) for AES followed by cryptogram Cipher DO 'A6' for ECDH
apdu += [0x00]
apdu += data
apdu += [0x02, 0x00]
let decipherApdu = YKFAPDU(data: Data(apdu))!
return decipherApdu
}
public static func get_application_related_data() -> YKFAPDU {
var apdu: [UInt8] = []
apdu += [0x00] // CLA
apdu += [0xCA] // INS: GET DATA
apdu += [0x00]
apdu += [0x6E] // P2: application related data
apdu += [0x00]
return YKFAPDU(data: Data(apdu))!
}
}

View file

@ -0,0 +1,63 @@
//
// YubiKeyConnection.swift
// passKit
//
// Copyright © 2022 Bob Sun. All rights reserved.
//
import Foundation
import YubiKit
public class YubiKeyConnection: NSObject {
public static let shared = YubiKeyConnection()
var accessoryConnection: YKFAccessoryConnection?
var nfcConnection: YKFNFCConnection?
var connectionCallback: ((_ connection: YKFConnectionProtocol) -> Void)?
var cancellationCallback: ((_ error: Error) -> Void)?
override init() {
super.init()
YubiKitManager.shared.delegate = self
YubiKitManager.shared.startAccessoryConnection()
}
public func connection(cancellation: @escaping (_ error: Error) -> Void, completion: @escaping (_ connection: YKFConnectionProtocol) -> Void) {
if let connection = accessoryConnection {
completion(connection)
} else {
connectionCallback = completion
if #available(iOSApplicationExtension 13.0, *) {
YubiKitManager.shared.startNFCConnection()
}
}
cancellationCallback = cancellation
}
}
extension YubiKeyConnection: YKFManagerDelegate {
public func didConnectNFC(_ connection: YKFNFCConnection) {
nfcConnection = connection
if let callback = connectionCallback {
callback(connection)
}
}
public func didDisconnectNFC(_: YKFNFCConnection, error _: Error?) {
nfcConnection = nil
}
public func didConnectAccessory(_ connection: YKFAccessoryConnection) {
accessoryConnection = connection
}
public func didDisconnectAccessory(_: YKFAccessoryConnection, error _: Error?) {
accessoryConnection = nil
}
public func didFailConnectingNFC(_ error: Error) {
if let callback = cancellationCallback {
callback(error)
}
}
}