Refactor YubiKey decryptor (#663)
- Add YKFSmartCardInterface extension to simplify smart card related calls - Use async/await to rewrite callback closures - Update YubiKeyConnection - Better error handling
This commit is contained in:
parent
fc35805565
commit
a410c9480a
9 changed files with 344 additions and 320 deletions
|
|
@ -36,12 +36,13 @@ public enum YubiKeyError: Error, Equatable {
|
|||
case selectApplication(message: String)
|
||||
case verify(message: String)
|
||||
case decipher(message: String)
|
||||
case other(message: String)
|
||||
}
|
||||
|
||||
extension YubiKeyError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case let .connection(message), let .decipher(message), let .selectApplication(message), let .verify(message):
|
||||
case let .connection(message), let .decipher(message), let .other(message), let .selectApplication(message), let .verify(message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +57,8 @@ extension AppError: LocalizedError {
|
|||
return localizationKey.localize(name)
|
||||
case let .pgpPrivateKeyNotFound(keyID), let .pgpPublicKeyNotFound(keyID):
|
||||
return localizationKey.localize(keyID)
|
||||
case let .yubiKey(error):
|
||||
return error.errorDescription
|
||||
case let .other(message):
|
||||
return message.localize()
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -13,30 +13,12 @@ public enum YubiKeyAPDU {
|
|||
}
|
||||
|
||||
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
|
||||
return YKFAPDU(data: Data(apdu))!
|
||||
YKFAPDU(cla: 0x00, ins: 0x20, p1: 0x00, p2: 0x82, data: Data(password.utf8), type: .extended)!
|
||||
}
|
||||
|
||||
public static func decipherExtended(data: Data) -> [YKFAPDU] {
|
||||
var apdu: [UInt8] = []
|
||||
apdu += [0x00] // CLA (last or only command of a chain)
|
||||
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]
|
||||
|
||||
return [YKFAPDU(data: Data(apdu))!]
|
||||
let apdu = YKFAPDU(cla: 0x00, ins: 0x2A, p1: 0x80, p2: 0x86, data: data, type: .extended)!
|
||||
return [apdu]
|
||||
}
|
||||
|
||||
public static func decipherChained(data: Data) -> [YKFAPDU] {
|
||||
|
|
@ -63,14 +45,8 @@ public enum YubiKeyAPDU {
|
|||
return result
|
||||
}
|
||||
|
||||
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))!
|
||||
public static func getApplicationRelatedData() -> YKFAPDU {
|
||||
YKFAPDU(cla: 0x00, ins: 0xCA, p1: 0x00, p2: 0x6E, data: Data(), type: .short)!
|
||||
}
|
||||
|
||||
static func chunk(data: Data) -> [[UInt8]] {
|
||||
|
|
|
|||
|
|
@ -2,62 +2,179 @@
|
|||
// YubiKeyConnection.swift
|
||||
// passKit
|
||||
//
|
||||
// Copyright © 2022 Bob Sun. All rights reserved.
|
||||
// Copyright (C) 2024 Mingshen Sun.
|
||||
//
|
||||
// This file is part of yubioath-ios, modified from the original.
|
||||
// Original code Copyright Yubico 2022.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
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() {
|
||||
override public init() {
|
||||
super.init()
|
||||
if YubiKitDeviceCapabilities.supportsISO7816NFCTags {
|
||||
YubiKitManager.shared.delegate = self
|
||||
YubiKitManager.shared.startAccessoryConnection()
|
||||
}
|
||||
YubiKitManager.shared.delegate = self
|
||||
}
|
||||
|
||||
public func connection(cancellation: @escaping (_ error: Error) -> Void, completion: @escaping (_ connection: YKFConnectionProtocol) -> Void) {
|
||||
deinit {}
|
||||
|
||||
var connection: YKFConnectionProtocol? {
|
||||
accessoryConnection ?? smartCardConnection ?? nfcConnection
|
||||
}
|
||||
|
||||
private var nfcConnection: YKFNFCConnection?
|
||||
private var smartCardConnection: YKFSmartCardConnection?
|
||||
private var accessoryConnection: YKFAccessoryConnection?
|
||||
|
||||
private var connectionCallback: ((_ connection: YKFConnectionProtocol) -> Void)?
|
||||
private var disconnectionCallback: ((_ connection: YKFConnectionProtocol?, _ error: Error?) -> Void)?
|
||||
|
||||
private var accessoryConnectionCallback: ((_ connection: YKFAccessoryConnection?) -> Void)?
|
||||
private var nfcConnectionCallback: ((_ connection: YKFNFCConnection?) -> Void)?
|
||||
private var smartCardConnectionCallback: ((_ connection: YKFSmartCardConnection?) -> Void)?
|
||||
|
||||
public func startConnection(completion: @escaping (_ connection: YKFConnectionProtocol) -> Void) {
|
||||
YubiKitManager.shared.delegate = self
|
||||
|
||||
if let connection = accessoryConnection {
|
||||
completion(connection)
|
||||
} else if let connection = smartCardConnection {
|
||||
completion(connection)
|
||||
} else if let connection = nfcConnection {
|
||||
completion(connection)
|
||||
} else {
|
||||
connectionCallback = completion
|
||||
YubiKitManager.shared.startNFCConnection()
|
||||
if YubiKitDeviceCapabilities.supportsISO7816NFCTags {
|
||||
YubiKitManager.shared.startNFCConnection()
|
||||
}
|
||||
}
|
||||
cancellationCallback = cancellation
|
||||
}
|
||||
|
||||
public func startConnection() async -> YKFConnectionProtocol {
|
||||
await withCheckedContinuation { continuation in
|
||||
self.startConnection { connection in
|
||||
continuation.resume(with: Result.success(connection))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startWiredConnection(completion: @escaping (_ connection: YKFConnectionProtocol) -> Void) {
|
||||
connectionCallback = completion
|
||||
YubiKitManager.shared.delegate = self
|
||||
}
|
||||
|
||||
func accessoryConnection(handler: @escaping (_ connection: YKFAccessoryConnection?) -> Void) {
|
||||
if let connection = accessoryConnection {
|
||||
handler(connection)
|
||||
} else {
|
||||
accessoryConnectionCallback = handler
|
||||
}
|
||||
}
|
||||
|
||||
func smartCardConnection(handler: @escaping (_ connection: YKFSmartCardConnection?) -> Void) {
|
||||
if let connection = smartCardConnection {
|
||||
handler(connection)
|
||||
} else {
|
||||
smartCardConnectionCallback = handler
|
||||
}
|
||||
}
|
||||
|
||||
func nfcConnection(handler: @escaping (_ connection: YKFNFCConnection?) -> Void) {
|
||||
if let connection = nfcConnection {
|
||||
handler(connection)
|
||||
} else {
|
||||
nfcConnectionCallback = handler
|
||||
}
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
if #available(iOSApplicationExtension 16.0, *) {
|
||||
smartCardConnection?.stop()
|
||||
}
|
||||
accessoryConnection?.stop()
|
||||
nfcConnection?.stop()
|
||||
// stop() returns immediately but closing the connection will take a few cycles so we need to wait to make sure it's closed before restarting.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
|
||||
YubiKitManager.shared.startAccessoryConnection()
|
||||
}
|
||||
if YubiKitDeviceCapabilities.supportsSmartCardOverUSBC, #available(iOSApplicationExtension 16.0, *) {
|
||||
YubiKitManager.shared.startSmartCardConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func didDisconnect(handler: @escaping (_ connection: YKFConnectionProtocol?, _ error: Error?) -> Void) {
|
||||
disconnectionCallback = handler
|
||||
}
|
||||
}
|
||||
|
||||
extension YubiKeyConnection: YKFManagerDelegate {
|
||||
public func didConnectNFC(_ connection: YKFNFCConnection) {
|
||||
nfcConnection = connection
|
||||
if let callback = connectionCallback {
|
||||
callback(connection)
|
||||
}
|
||||
nfcConnectionCallback?(connection)
|
||||
nfcConnectionCallback = nil
|
||||
connectionCallback?(connection)
|
||||
connectionCallback = nil
|
||||
}
|
||||
|
||||
public func didDisconnectNFC(_: YKFNFCConnection, error _: Error?) {
|
||||
public func didDisconnectNFC(_ connection: YKFNFCConnection, error: Error?) {
|
||||
nfcConnection = nil
|
||||
nfcConnectionCallback = nil
|
||||
connectionCallback = nil
|
||||
disconnectionCallback?(connection, error)
|
||||
disconnectionCallback = nil
|
||||
}
|
||||
|
||||
public func didFailConnectingNFC(_ error: Error) {
|
||||
nfcConnectionCallback = nil
|
||||
connectionCallback = nil
|
||||
disconnectionCallback?(nil, error)
|
||||
disconnectionCallback = nil
|
||||
}
|
||||
|
||||
public func didConnectAccessory(_ connection: YKFAccessoryConnection) {
|
||||
accessoryConnection = connection
|
||||
accessoryConnectionCallback?(connection)
|
||||
accessoryConnectionCallback = nil
|
||||
connectionCallback?(connection)
|
||||
connectionCallback = nil
|
||||
}
|
||||
|
||||
public func didDisconnectAccessory(_: YKFAccessoryConnection, error _: Error?) {
|
||||
public func didDisconnectAccessory(_ connection: YKFAccessoryConnection, error: Error?) {
|
||||
accessoryConnection = nil
|
||||
accessoryConnectionCallback = nil
|
||||
connectionCallback = nil
|
||||
disconnectionCallback?(connection, error)
|
||||
disconnectionCallback = nil
|
||||
}
|
||||
|
||||
public func didFailConnectingNFC(_ error: Error) {
|
||||
if let callback = cancellationCallback {
|
||||
callback(error)
|
||||
}
|
||||
public func didConnectSmartCard(_ connection: YKFSmartCardConnection) {
|
||||
smartCardConnection = connection
|
||||
smartCardConnectionCallback?(connection)
|
||||
smartCardConnectionCallback = nil
|
||||
connectionCallback?(connection)
|
||||
connectionCallback = nil
|
||||
}
|
||||
|
||||
public func didDisconnectSmartCard(_ connection: YKFSmartCardConnection, error: Error?) {
|
||||
smartCardConnection = nil
|
||||
smartCardConnectionCallback = nil
|
||||
connectionCallback = nil
|
||||
disconnectionCallback?(connection, error)
|
||||
disconnectionCallback = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue