prevent crashes by catching exceptions

This commit is contained in:
Adam Velebil 2022-05-13 16:46:50 +02:00
parent e86c50fb64
commit 8b8cdf40bb
No known key found for this signature in database
GPG Key ID: AC6D6B9D715FC084
5 changed files with 179 additions and 85 deletions

View File

@ -1,51 +0,0 @@
package com.yubico.authenticator.data.device
import com.yubico.yubikit.core.Transport
import com.yubico.yubikit.core.Version
import com.yubico.yubikit.management.DeviceConfig
import com.yubico.yubikit.management.DeviceInfo
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
fun DeviceConfig.toJson() = JsonObject(
mapOf(
"device_flags" to JsonPrimitive(deviceFlags),
"challenge_response_timeout" to JsonPrimitive(challengeResponseTimeout),
"auto_eject_timeout" to JsonPrimitive(autoEjectTimeout),
"enabled_capabilities" to JsonObject(
mapOf(
"usb" to JsonPrimitive(getEnabledCapabilities(Transport.USB) ?: 0),
"nfc" to JsonPrimitive(getEnabledCapabilities(Transport.NFC) ?: 0),
)
)
)
)
fun Version.toJson() = JsonArray(
listOf(
JsonPrimitive(major),
JsonPrimitive(minor),
JsonPrimitive(micro)
)
)
fun DeviceInfo.toJson(name: String, isNfcDevice: Boolean) = JsonObject(
mapOf(
"config" to config.toJson(),
"serial" to JsonPrimitive(serialNumber),
"version" to version.toJson(),
"form_factor" to JsonPrimitive(formFactor.value),
"is_locked" to JsonPrimitive(isLocked),
"is_sky" to JsonPrimitive(isSky),
"is_fips" to JsonPrimitive(isFips),
"name" to JsonPrimitive(name),
"is_nfc" to JsonPrimitive(isNfcDevice),
"supported_capabilities" to JsonObject(
mapOf(
"usb" to JsonPrimitive(getSupportedCapabilities(Transport.USB)),
"nfc" to JsonPrimitive(getSupportedCapabilities(Transport.NFC)),
)
)
)
)

View File

@ -0,0 +1,34 @@
package com.yubico.authenticator.management
import com.yubico.yubikit.core.Transport
import com.yubico.yubikit.core.Version
import com.yubico.yubikit.management.DeviceConfig
import com.yubico.yubikit.management.DeviceInfo
fun DeviceConfig.model() = Model.DeviceConfig(
deviceFlags = deviceFlags,
challengeResponseTimeout = challengeResponseTimeout,
autoEjectTimeout = autoEjectTimeout,
enabledCapabilities = mapOf(
"usb" to (getEnabledCapabilities(Transport.USB) ?: 0),
"nfc" to (getEnabledCapabilities(Transport.NFC) ?: 0)
)
)
fun DeviceInfo.model(name: String, isNfc: Boolean, usbPid: Int?) = Model.AppDeviceInfo(
config = config.model(),
serialNumber = serialNumber,
version = listOf(version.major, version.minor, version.micro),
formFactor = formFactor.value,
isLocked = isLocked,
isSky = isSky,
isFips = isFips,
name = name,
isNfc = isNfc,
usbPid = usbPid,
supportedCapabilities = mapOf(
"usb" to getSupportedCapabilities(Transport.USB),
"nfc" to getSupportedCapabilities(Transport.NFC),
)
)

View File

@ -0,0 +1,46 @@
package com.yubico.authenticator.management
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
class Model {
@Serializable
data class DeviceConfig(
@SerialName("device_flags")
val deviceFlags: Int?,
@SerialName("challenge_response_timeout")
val challengeResponseTimeout: Byte?,
@SerialName("auto_eject_timeout")
val autoEjectTimeout: Short?,
@SerialName("enabled_capabilities")
val enabledCapabilities: Map<String, Int>
)
@Serializable
data class AppDeviceInfo(
@SerialName("config")
val config: DeviceConfig,
@SerialName("serial")
val serialNumber: Int?,
@SerialName("version")
val version: List<Byte>,
@SerialName("form_factor")
val formFactor: Int,
@SerialName("is_locked")
val isLocked: Boolean,
@SerialName("is_sky")
val isSky: Boolean,
@SerialName("is_fips")
val isFips: Boolean,
@SerialName("name")
val name: String,
@SerialName("is_nfc")
val isNfc: Boolean,
@SerialName("usb_pid")
val usbPid: Int?,
@SerialName("supported_capabilities")
val supportedCapabilities: Map<String, Int>
)
}

View File

@ -6,20 +6,25 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.yubico.authenticator.*
import com.yubico.authenticator.api.Pigeon.*
import com.yubico.authenticator.data.device.toJson
import com.yubico.authenticator.logging.Log
import com.yubico.authenticator.management.model
import com.yubico.authenticator.management.Model.AppDeviceInfo
import com.yubico.authenticator.oath.keystore.ClearingMemProvider
import com.yubico.authenticator.oath.keystore.KeyStoreProvider
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
import com.yubico.yubikit.core.Logger
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.fido.FidoConnection
import com.yubico.yubikit.core.otp.OtpConnection
import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.oath.*
import com.yubico.yubikit.support.DeviceUtil
import io.flutter.plugin.common.BinaryMessenger
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.IOException
import java.net.URI
import java.util.concurrent.Executors
import kotlin.coroutines.resume
@ -99,20 +104,18 @@ class OathManager(
_isUsbKey = device is UsbYubiKeyDevice
try {
coroutineScope.launch {
if (pendingYubiKeyAction.value != null) {
provideYubiKey(com.yubico.yubikit.core.util.Result.success(device))
} else {
withContext(Dispatchers.Main) {
sendDeviceInfo(device)
sendOathInfo(device)
sendOathCodes(device)
}
}
val handler = CoroutineExceptionHandler { _, throwable ->
Log.e(TAG, "Exception caught: ${throwable.message}")
}
coroutineScope.launch(handler) {
if (pendingYubiKeyAction.value != null) {
provideYubiKey(com.yubico.yubikit.core.util.Result.success(device))
} else {
sendDeviceInfo(device)
sendOathInfo(device)
sendOathCodes(device)
}
} catch (illegalStateException: IllegalStateException) {
// ignored
}
}
@ -391,27 +394,84 @@ class OathManager(
}
}
private suspend fun sendDeviceInfo(device: YubiKeyDevice) {
val deviceInfoData = suspendCoroutine<String> {
device.requestConnection(SmartCardConnection::class.java) { result ->
try {
val pid = (device as? UsbYubiKeyDevice)?.pid
val deviceInfo = DeviceUtil.readInfo(result.value, pid)
val name = DeviceUtil.getName(deviceInfo, pid?.type)
val deviceInfoData = deviceInfo
.toJson(name, device is NfcYubiKeyDevice)
.toString()
it.resume(deviceInfoData)
} catch (cause: Throwable) {
Logger.e("Failed to get device info", cause)
it.resumeWithException(cause)
private suspend fun <T> withSmartCardConnection(
device: YubiKeyDevice,
block: (SmartCardConnection) -> T
) =
suspendCoroutine<T> { continuation ->
device.requestConnection(SmartCardConnection::class.java) {
if (it.isError) {
continuation.resumeWithException(IllegalStateException("Failed to get SmartCardConnection"))
} else {
continuation.resume(block(it.value))
}
}
}
_fManagementApi.updateDeviceInfo(deviceInfoData) {}
private suspend fun <T> withOTPConnection(device: YubiKeyDevice, block: (OtpConnection) -> T) =
suspendCoroutine<T> { continuation ->
device.requestConnection(OtpConnection::class.java) {
if (it.isError) {
continuation.resumeWithException(IllegalStateException("Failed to get OtpConnection"))
} else {
continuation.resume(block(it.value))
}
}
}
private suspend fun <T> withFidoConnection(
device: YubiKeyDevice,
block: (FidoConnection) -> T
) =
suspendCoroutine<T> { continuation ->
device.requestConnection(FidoConnection::class.java) {
if (it.isError) {
continuation.resumeWithException(IllegalStateException("Failed to get FidoConnection"))
} else {
continuation.resume(block(it.value))
}
}
}
private suspend fun getDeviceInfo(device: YubiKeyDevice): AppDeviceInfo =
try {
withSmartCardConnection(device) {
val pid = (device as? UsbYubiKeyDevice)?.pid
val deviceInfo = DeviceUtil.readInfo(it, pid)
val name = DeviceUtil.getName(deviceInfo, pid?.type)
deviceInfo.model(name, device is NfcYubiKeyDevice, pid?.value)
}
} catch (exception: Exception) {
Log.d(TAG, "Smart card connection not available")
try {
withOTPConnection(device) {
val pid = (device as? UsbYubiKeyDevice)?.pid
val deviceInfo = DeviceUtil.readInfo(it, pid)
val name = DeviceUtil.getName(deviceInfo, pid?.type)
deviceInfo.model(name, device is NfcYubiKeyDevice, pid?.value)
}
} catch (exception: Exception) {
Log.d(TAG, "OTP connection not available")
try {
withFidoConnection(device) {
val pid = (device as? UsbYubiKeyDevice)?.pid
val deviceInfo = DeviceUtil.readInfo(it, pid)
val name = DeviceUtil.getName(deviceInfo, pid?.type)
deviceInfo.model(name, device is NfcYubiKeyDevice, pid?.value)
}
} catch (exception: Exception) {
Log.e(TAG, "No connection available for getting device info")
throw exception
}
}
}
private suspend fun sendDeviceInfo(device: YubiKeyDevice) {
val deviceInfoData = getDeviceInfo(device)
withContext(Dispatchers.Main) {
Log.d(TAG, "Sending device info: $deviceInfoData")
_fManagementApi.updateDeviceInfo(Json.encodeToString(deviceInfoData)) {}
}
}
private suspend fun sendOathInfo(device: YubiKeyDevice) {
@ -448,7 +508,9 @@ class OathManager(
}
}
_fOathApi.updateSession(oathSessionData) {}
withContext(Dispatchers.Main) {
_fOathApi.updateSession(oathSessionData) {}
}
}
private suspend fun sendOathCodes(device: YubiKeyDevice) {
@ -466,7 +528,9 @@ class OathManager(
}
}
_fOathApi.updateOathCredentials(sendOathCodes) {}
withContext(Dispatchers.Main) {
_fOathApi.updateOathCredentials(sendOathCodes) {}
}
}
/**

View File

@ -37,13 +37,14 @@ class _YubikeyProvider extends StateNotifier<YubiKeyData?> {
DeviceInfo deviceInfo = DeviceInfo.fromJson(args);
String name = args['name'];
bool isNfc = args['is_nfc'];
int? usbPid = args['usb_pid'];
DeviceNode deviceNode = isNfc
? DeviceNode.nfcReader(DevicePath([]), name)
: DeviceNode.usbYubiKey(
DevicePath([]),
name,
/*TODO: replace with correct PID*/ UsbPid.yk4OtpFidoCcid,
usbPid != null ? UsbPid.fromValue(usbPid) : UsbPid.yk4OtpFidoCcid,
deviceInfo);
// reset oath providers on key change