read device info on connect

This commit is contained in:
Adam Velebil 2024-04-03 17:54:34 +02:00
parent 11cb26ac5c
commit 2038ec0012
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
4 changed files with 70 additions and 107 deletions

View File

@ -40,6 +40,7 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.google.android.material.color.DynamicColors
import com.yubico.authenticator.device.DeviceManager
import com.yubico.authenticator.device.UnknownDevice
import com.yubico.authenticator.fido.FidoManager
import com.yubico.authenticator.fido.FidoViewModel
import com.yubico.authenticator.logging.FlutterLog
@ -47,11 +48,13 @@ import com.yubico.authenticator.management.ManagementHandler
import com.yubico.authenticator.oath.AppLinkMethodChannel
import com.yubico.authenticator.oath.OathManager
import com.yubico.authenticator.oath.OathViewModel
import com.yubico.authenticator.yubikit.getDeviceInfo
import com.yubico.yubikit.android.YubiKitManager
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
import com.yubico.yubikit.android.transport.usb.UsbConfiguration
import com.yubico.yubikit.core.Transport
import com.yubico.yubikit.core.YubiKeyDevice
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
@ -273,24 +276,36 @@ class MainActivity : FlutterFragmentActivity() {
private fun processYubiKey(device: YubiKeyDevice) {
lifecycleScope.launch {
if (device is NfcYubiKeyDevice) {
// verify that current context supports connection provided by the YubiKey
// if not, switch to a context which supports the connection
val supportedApps = DeviceManager.getSupportedContexts(device)
logger.debug("Connected key supports: {}", supportedApps)
if (!supportedApps.contains(viewModel.appContext.value)) {
val preferredContext = DeviceManager.getPreferredContext(supportedApps)
logger.debug(
"Current context ({}) is not supported by the key. Using preferred context {}",
viewModel.appContext.value,
preferredContext
)
switchContext(preferredContext)
}
val deviceInfo = try {
getDeviceInfo(device)
} catch (e: IllegalArgumentException) {
logger.debug("Device was not recognized")
UnknownDevice.copy(isNfc = device.transport == Transport.NFC)
} catch (e: Exception) {
logger.error("Failure getting device info", e)
null
}
if (contextManager == null) {
switchContext(DeviceManager.getPreferredContext(supportedApps))
}
deviceManager.setDeviceInfo(deviceInfo)
if (deviceInfo == null) {
return@launch
}
val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo)
logger.debug("Connected key supports: {}", supportedContexts)
if (!supportedContexts.contains(viewModel.appContext.value)) {
val preferredContext = DeviceManager.getPreferredContext(supportedContexts)
logger.debug(
"Current context ({}) is not supported by the key. Using preferred context {}",
viewModel.appContext.value,
preferredContext
)
switchContext(preferredContext)
}
if (contextManager == null) {
switchContext(DeviceManager.getPreferredContext(supportedContexts))
}
contextManager?.let {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2024 Yubico.
*
* 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.
*/
package com.yubico.authenticator.device
import androidx.collection.ArraySet
@ -8,10 +24,7 @@ import com.yubico.authenticator.MainViewModel
import com.yubico.authenticator.OperationContext
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.fido.FidoConnection
import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.fido.ctap.Ctap2Session
import com.yubico.yubikit.oath.OathSession
import com.yubico.yubikit.management.Capability
import org.slf4j.LoggerFactory
interface DeviceListener {
@ -45,49 +58,36 @@ class DeviceManager(
const val NFC_DATA_CLEANUP_DELAY = 30L * 1000 // 30s
private val logger = LoggerFactory.getLogger(DeviceManager::class.java)
fun getSupportedContexts(device: YubiKeyDevice) : ArraySet<OperationContext> = try {
private val capabilityContextMap = mapOf(
Capability.OATH to listOf(OperationContext.Oath),
Capability.FIDO2 to listOf(
OperationContext.FidoFingerprints,
OperationContext.FidoPasskeys
)
)
fun getSupportedContexts(deviceInfo: Info): ArraySet<OperationContext> {
val operationContexts = ArraySet<OperationContext>()
if (device.supportsConnection(SmartCardConnection::class.java)) {
// try which apps are available
device.openConnection(SmartCardConnection::class.java).use {
try {
OathSession(it)
operationContexts.add(OperationContext.Oath)
} catch (e: Throwable) { // ignored
}
val capabilities = (
if (deviceInfo.isNfc)
deviceInfo.config.enabledCapabilities.nfc else
deviceInfo.config.enabledCapabilities.usb
) ?: 0
try {
Ctap2Session(it)
operationContexts.add(OperationContext.FidoPasskeys)
} catch (e: Throwable) { // ignored
}
}
}
if (device.supportsConnection(FidoConnection::class.java)) {
device.openConnection(FidoConnection::class.java).use {
try {
Ctap2Session(it)
operationContexts.add(OperationContext.FidoPasskeys)
operationContexts.add(OperationContext.FidoFingerprints)
} catch (e: Throwable) { // ignored
}
capabilityContextMap.forEach { entry ->
if (capabilities and entry.key.bit == entry.key.bit) {
operationContexts.addAll(entry.value)
}
}
logger.debug("Device supports following contexts: {}", operationContexts)
operationContexts
} catch(e: Exception) {
logger.debug("The device does not support any context. The following exception was caught: ", e)
ArraySet<OperationContext>()
return operationContexts
}
fun getPreferredContext(contexts: ArraySet<OperationContext>) : OperationContext {
fun getPreferredContext(contexts: ArraySet<OperationContext>): OperationContext {
// custom sort
for(context in contexts) {
for (context in contexts) {
if (context == OperationContext.Oath) {
return context
} else if (context == OperationContext.FidoPasskeys) {

View File

@ -188,21 +188,7 @@ class FidoManager(
}
} catch (e: Exception) {
// something went wrong, try to get DeviceInfo from any available connection type
logger.error("Failure when processing YubiKey", e)
if (device.transport == Transport.USB || e is ApplicationNotAvailableException) {
val deviceInfo = try {
getDeviceInfo(device)
} catch (e: IllegalArgumentException) {
logger.debug("Device was not recognized")
UnknownDevice.copy(isNfc = device.transport == Transport.NFC)
} catch (e: Exception) {
logger.error("Failure getting device info", e)
null
}
logger.debug("Setting device info: {}", deviceInfo)
deviceManager.setDeviceInfo(deviceInfo)
}
logger.error("Failure when processing YubiKey: ", e)
// Clear any cached FIDO state
fidoViewModel.clearSessionState()
@ -244,18 +230,6 @@ class FidoManager(
fidoSession.cachedInfo, pinStore.hasPin()
)
)
// Update deviceInfo since the deviceId has changed
val pid = (device as? UsbYubiKeyDevice)?.pid
val deviceInfo = DeviceUtil.readInfo(connection, pid)
deviceManager.setDeviceInfo(
Info(
name = DeviceUtil.getName(deviceInfo, pid?.type),
isNfc = device.transport == Transport.NFC,
usbPid = pid?.value,
deviceInfo = deviceInfo
)
)
}
}

View File

@ -282,18 +282,6 @@ class OathManager(
return@withConnection
}
}
// Update deviceInfo since the deviceId has changed
val pid = (device as? UsbYubiKeyDevice)?.pid
val deviceInfo = DeviceUtil.readInfo(connection, pid)
deviceManager.setDeviceInfo(
Info(
name = DeviceUtil.getName(deviceInfo, pid?.type),
isNfc = device.transport == Transport.NFC,
usbPid = pid?.value,
deviceInfo = deviceInfo
)
)
}
}
logger.debug(
@ -301,21 +289,7 @@ class OathManager(
)
} catch (e: Exception) {
// OATH not enabled/supported, try to get DeviceInfo over other USB interfaces
logger.error("Failed to connect to CCID", e)
if (device.transport == Transport.USB || e is ApplicationNotAvailableException) {
val deviceInfo = try {
getDeviceInfo(device)
} catch (e: IllegalArgumentException) {
logger.debug("Device was not recognized")
UnknownDevice.copy(isNfc = device.transport == Transport.NFC)
} catch (e: Exception) {
logger.error("Failure getting device info", e)
null
}
logger.debug("Setting device info: {}", deviceInfo)
deviceManager.setDeviceInfo(deviceInfo)
}
logger.error("Failed to connect to CCID: ", e)
// Clear any cached OATH state
oathViewModel.clearSession()