From c32ab802ead63ebdebb8a16bd22e1b1f11e70759 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 25 Aug 2022 13:54:34 +0200 Subject: [PATCH] get SKY info from usbDevice --- .../authenticator/yubikit/DeviceInfoHelper.kt | 5 +- .../yubico/authenticator/yubikit/SkyHelper.kt | 111 ++++++++++++++++++ lib/app/views/device_utils.dart | 9 +- 3 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 android/app/src/main/kotlin/com/yubico/authenticator/yubikit/SkyHelper.kt diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt index 7c975760..d17468f2 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt @@ -24,8 +24,11 @@ suspend fun getDeviceInfo(device: YubiKeyDevice): Info { }.recoverCatching { Log.d(OathManager.TAG, "OTP connection not available") device.withConnection { DeviceUtil.readInfo(it, pid) } + }.recoverCatching { + Log.d(OathManager.TAG, "FIDO connection not available") + return SkyHelper.getDeviceInfo(device) }.getOrElse { - Log.e(OathManager.TAG, "No connection available for getting device info") + Log.e(OathManager.TAG, "Failed to recognize device") throw it } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/SkyHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/SkyHelper.kt new file mode 100644 index 00000000..764fedf8 --- /dev/null +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/SkyHelper.kt @@ -0,0 +1,111 @@ +package com.yubico.authenticator.yubikit + +import com.yubico.authenticator.device.Info +import com.yubico.authenticator.management.model +import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice +import com.yubico.yubikit.core.Transport +import com.yubico.yubikit.core.UsbPid +import com.yubico.yubikit.core.Version +import com.yubico.yubikit.core.YubiKeyDevice +import com.yubico.yubikit.management.DeviceConfig +import com.yubico.yubikit.management.DeviceInfo +import com.yubico.yubikit.management.FormFactor +import java.util.regex.Pattern + +class SkyHelper { + companion object { + private val VERSION_0 = Version(0, 0, 0) + private val VERSION_3 = Version(3, 0, 0) + private val VERSION_4 = Version(4, 0, 0) + private val VERSION_5 = Version(5, 0, 0) + private val VERSION_6 = Version(6, 0, 0) + + private val USB_VERSION_STRING_PATTERN: Pattern = + Pattern.compile("\\b(\\d{1,3})\\.(\\d)(\\d+)\\b") + + /** + * Retrieves a [DeviceInfo] from USB Security YubiKey (SKY). + * + * Should be only used as last resort when all other DeviceInfo queries failed because + * the returned information might not be accurate. + * + * @param device YubiKeyDevice to get DeviceInfo for. Should be USB and SKY device + * @return [DeviceInfo] instance initialized with information from USB descriptors. + * @throws IllegalArgumentException if [device] is not instance of [UsbYubiKeyDevice] or + * if the USB device has wrong PID + */ + fun getDeviceInfo(device: YubiKeyDevice): Info { + if (device !is UsbYubiKeyDevice) { + throw IllegalArgumentException() + } + + val pid = device.pid + + if (pid !in listOf(UsbPid.YK4_FIDO, UsbPid.SKY_FIDO, UsbPid.NEO_FIDO)) { + throw IllegalArgumentException() + } + + val usbVersion = validateVersionForPid(getVersionFromUsbDescriptor(device), pid) + + // build DeviceInfo containing only USB product name and USB version + // we assume this is a Security Key based on the USB PID + return DeviceInfo( + DeviceConfig.Builder().enabledCapabilities(Transport.USB, 0).build(), + null, + usbVersion, + FormFactor.UNKNOWN, + mapOf(Transport.USB to 0), + false, + false, + true + ).model(device.usbDevice.productName ?: "YubiKey Security Key", false, pid.value) + } + + // try to convert USB version to YubiKey version + private fun getVersionFromUsbDescriptor(device: UsbYubiKeyDevice): Version { + val version = device.usbDevice.version + + try { + return Version.parse(version) + } catch (_: IllegalArgumentException) { + val match = USB_VERSION_STRING_PATTERN.matcher(version) + if (match.find()) { + val major = match.group(1)?.toByte() ?: 0 + val minor = match.group(2)?.toByte() ?: 0 + val patch = match.group(3)?.toByte() ?: 0 + return Version(major, minor, patch) + } + } + + return VERSION_0 + } + + /** + * Check whether usbVersion is in expected range defined by UsbPid + * + * @return original version or [Version(0,0,0)] indicating invalid/unknown version + */ + private fun validateVersionForPid(usbVersion: Version, pid: UsbPid): Version { + if (pid == UsbPid.NEO_FIDO && !usbVersion.inRange(VERSION_3, VERSION_4)) { + return VERSION_0 + } + + if (pid == UsbPid.SKY_FIDO && !usbVersion.inRange(VERSION_4, VERSION_5)) { + return VERSION_0 + } + + if (pid == UsbPid.YK4_FIDO && !usbVersion.inRange(VERSION_5, VERSION_6)) { + return VERSION_0 + } + + return usbVersion + } + + /** Check if this version is at least v1 and less than v2 + * @return true if in range [v1,v2) + */ + private fun Version.inRange(v1: Version, v2: Version) : Boolean { + return this >= v1 && this < v2 + } + } +} \ No newline at end of file diff --git a/lib/app/views/device_utils.dart b/lib/app/views/device_utils.dart index 94a201dd..2066f709 100755 --- a/lib/app/views/device_utils.dart +++ b/lib/app/views/device_utils.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yubico_authenticator/core/models.dart'; import '../../management/models.dart'; import '../models.dart'; @@ -9,7 +10,11 @@ String getDeviceInfoString(DeviceInfo info) { if (serial != null) { subtitle += 'S/N: $serial '; } - subtitle += 'F/W: ${info.version}'; + if (info.version.isAtLeast(1)) { + subtitle += 'F/W: ${info.version}'; + } else { + subtitle += 'Unknown type'; + } return subtitle; } @@ -24,7 +29,7 @@ List getDeviceMessages(DeviceNode? node, AsyncValue data) { case 'unknown-device': return ['Unrecognized device']; case 'device-inaccessible': - return ['Device inacessible']; + return ['Device inaccessible']; } return null; },