diff --git a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt index 409b6ae..75a74c2 100644 --- a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt @@ -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() diff --git a/app/src/main/java/pl/lebihan/authnkey/FidoTransport.kt b/app/src/main/java/pl/lebihan/authnkey/FidoTransport.kt index 221be46..d09ebc4 100644 --- a/app/src/main/java/pl/lebihan/authnkey/FidoTransport.kt +++ b/app/src/main/java/pl/lebihan/authnkey/FidoTransport.kt @@ -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 diff --git a/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt b/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt index e2f5d40..02da4a1 100644 --- a/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt @@ -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 diff --git a/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt b/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt index 79e3553..55fce57 100644 --- a/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt +++ b/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt @@ -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() diff --git a/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt b/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt index ae2ba95..80181e9 100644 --- a/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt +++ b/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt @@ -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)