get SKY info from usbDevice

This commit is contained in:
Adam Velebil 2022-08-25 13:54:34 +02:00
parent ab0e76d24c
commit c32ab802ea
No known key found for this signature in database
GPG Key ID: AC6D6B9D715FC084
3 changed files with 122 additions and 3 deletions

View File

@ -24,8 +24,11 @@ suspend fun getDeviceInfo(device: YubiKeyDevice): Info {
}.recoverCatching { }.recoverCatching {
Log.d(OathManager.TAG, "OTP connection not available") Log.d(OathManager.TAG, "OTP connection not available")
device.withConnection<FidoConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) } device.withConnection<FidoConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) }
}.recoverCatching {
Log.d(OathManager.TAG, "FIDO connection not available")
return SkyHelper.getDeviceInfo(device)
}.getOrElse { }.getOrElse {
Log.e(OathManager.TAG, "No connection available for getting device info") Log.e(OathManager.TAG, "Failed to recognize device")
throw it throw it
} }

View File

@ -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
}
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:yubico_authenticator/core/models.dart';
import '../../management/models.dart'; import '../../management/models.dart';
import '../models.dart'; import '../models.dart';
@ -9,7 +10,11 @@ String getDeviceInfoString(DeviceInfo info) {
if (serial != null) { if (serial != null) {
subtitle += 'S/N: $serial '; subtitle += 'S/N: $serial ';
} }
if (info.version.isAtLeast(1)) {
subtitle += 'F/W: ${info.version}'; subtitle += 'F/W: ${info.version}';
} else {
subtitle += 'Unknown type';
}
return subtitle; return subtitle;
} }
@ -24,7 +29,7 @@ List<String> getDeviceMessages(DeviceNode? node, AsyncValue<YubiKeyData> data) {
case 'unknown-device': case 'unknown-device':
return ['Unrecognized device']; return ['Unrecognized device'];
case 'device-inaccessible': case 'device-inaccessible':
return ['Device inacessible']; return ['Device inaccessible'];
} }
return null; return null;
}, },