Detect stale USB connections on resume

This commit is contained in:
mimi89999
2026-02-16 14:25:29 +01:00
parent da56420912
commit 61f7a95d07
5 changed files with 50 additions and 5 deletions
@@ -328,7 +328,7 @@ class CredentialProviderActivity : AppCompatActivity() {
registerReceiver(usbAttachReceiver, usbAttachFilter)
}
if (currentTransport == null) {
if (currentTransport?.isConnected != true || currentTransport is UsbTransport) {
checkForUsbDevice()
}
}
@@ -374,6 +374,19 @@ class CredentialProviderActivity : AppCompatActivity() {
}
private fun checkForUsbDevice() {
// Verify the existing USB connection is still usable
val transport = currentTransport
if (transport is UsbTransport) {
try {
transport.reclaimConnection()
return // still good
} catch (e: AuthnkeyError.NotConnected) {
transport.close()
currentTransport = null
pinProtocol = null
}
}
val devices = usbManager.deviceList.values.filter { UsbTransport.isFidoDevice(it) }
if (devices.isNotEmpty()) {
val device = devices.first()
@@ -15,6 +15,14 @@ interface FidoTransport {
val transportType: TransportType
val isConnected: Boolean
/**
* Verify that the transport connection is still active and usable.
* Throws [AuthnkeyError.NotConnected] if the connection has been lost
* (e.g. another app claimed the USB interface, or an NFC tag moved out of range).
*/
@Throws(AuthnkeyError.NotConnected::class)
fun reclaimConnection()
@Throws(Exception::class)
suspend fun sendCtapCommand(command: ByteArray): ByteArray
@@ -208,7 +208,9 @@ class MainActivity : AppCompatActivity() {
}
// Auto-connect to already-plugged USB FIDO devices
checkForUsbDevice()
if (currentTransport?.isConnected != true || currentTransport is UsbTransport) {
checkForUsbDevice()
}
// Check if started by USB device attachment
if (intent.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) {
@@ -318,9 +320,19 @@ class MainActivity : AppCompatActivity() {
* Only connects if there's exactly one device and we're not already connected.
*/
private fun checkForUsbDevice() {
// Skip if already connected
if (currentTransport?.isConnected == true) {
return
// Verify the existing USB connection is still usable
val transport = currentTransport
if (transport is UsbTransport) {
try {
transport.reclaimConnection()
return // still good
} catch (e: AuthnkeyError.NotConnected) {
transport.close()
currentTransport = null
pinProtocol = null
credentialManagement = null
updateConnectionStatus()
}
}
val devices = usbManager.deviceList.values
@@ -19,6 +19,12 @@ class NfcTransport(private val isoDep: IsoDep) : FidoTransport {
false
}
override fun reclaimConnection() {
if (!isConnected) {
throw AuthnkeyError.NotConnected()
}
}
init {
if (!isoDep.isConnected) {
isoDep.connect()
@@ -56,6 +56,12 @@ class UsbTransport(
}
}
override fun reclaimConnection() {
if (!_isConnected || !connection.claimInterface(hidInterface, false)) {
throw AuthnkeyError.NotConnected()
}
}
override suspend fun sendCtapCommand(command: ByteArray): ByteArray = withContext(Dispatchers.IO) {
// CTAPHID_CBOR command
sendRaw(channelId, CMD_CBOR, command)