From f09dade05aaab86a97b1bdbe0c7e1eda60d0c0ab Mon Sep 17 00:00:00 2001 From: mimi89999 Date: Wed, 25 Mar 2026 23:30:36 +0100 Subject: [PATCH] Unify NFC and USB transport construction --- .../authnkey/CredentialProviderActivity.kt | 11 +++--- .../java/pl/lebihan/authnkey/MainActivity.kt | 11 +++--- .../java/pl/lebihan/authnkey/NfcTransport.kt | 34 +++++++++++++------ .../java/pl/lebihan/authnkey/UsbTransport.kt | 26 +++++++------- 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt index 75a74c2..70384ae 100644 --- a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt @@ -421,11 +421,8 @@ class CredentialProviderActivity : AppCompatActivity() { bottomSheet?.getCurrentPinIfValid()?.let { pendingPin = it } bottomSheet?.showPinInput(false) - val isoDep = IsoDep.get(tag) ?: throw AuthnkeyError.NotIsoDepTag() - val transport = NfcTransport(isoDep) - - if (!transport.selectFidoApplet()) { - throw AuthnkeyError.FidoAppletNotFound() + val transport = withContext(Dispatchers.IO) { + NfcTransport.connect(tag) } currentTransport = transport @@ -464,8 +461,8 @@ class CredentialProviderActivity : AppCompatActivity() { setState(CredentialBottomSheet.State.PROCESSING) val transport = withContext(Dispatchers.IO) { - UsbTransport.create(usbManager, device) - } ?: throw AuthnkeyError.ConnectionFailed() + UsbTransport.connect(usbManager, device) + } currentTransport = transport pinProtocol = PinProtocol(transport) diff --git a/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt b/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt index 02da4a1..d405160 100644 --- a/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt @@ -266,11 +266,8 @@ class MainActivity : AppCompatActivity() { currentTransport?.close() currentTransport = null - val isoDep = IsoDep.get(tag) ?: throw AuthnkeyError.NotIsoDepTag() - val transport = NfcTransport(isoDep) - - if (!transport.selectFidoApplet()) { - throw AuthnkeyError.FidoAppletNotFound() + val transport = withContext(Dispatchers.IO) { + NfcTransport.connect(tag) } currentTransport = transport @@ -374,8 +371,8 @@ class MainActivity : AppCompatActivity() { statusText.text = getString(R.string.connecting_usb) val transport = withContext(Dispatchers.IO) { - UsbTransport.create(usbManager, device) - } ?: throw AuthnkeyError.ConnectionFailed() + UsbTransport.connect(usbManager, device) + } currentTransport = transport pinProtocol = PinProtocol(transport) diff --git a/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt b/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt index 55fce57..90f291d 100644 --- a/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt +++ b/app/src/main/java/pl/lebihan/authnkey/NfcTransport.kt @@ -1,5 +1,6 @@ package pl.lebihan.authnkey +import android.nfc.Tag import android.nfc.TagLostException import android.nfc.tech.IsoDep import kotlinx.coroutines.Dispatchers @@ -8,7 +9,7 @@ import kotlinx.coroutines.withContext /** * FIDO transport over NFC using ISO 7816-4 APDUs */ -class NfcTransport(private val isoDep: IsoDep) : FidoTransport { +class NfcTransport private constructor(private val isoDep: IsoDep) : FidoTransport { override val transportType = TransportType.NFC @@ -25,17 +26,10 @@ class NfcTransport(private val isoDep: IsoDep) : FidoTransport { } } - init { - if (!isoDep.isConnected) { - isoDep.connect() - } - isoDep.timeout = 5000 - } - /** * Select the FIDO applet on the NFC device */ - suspend fun selectFidoApplet(): Boolean = withContext(Dispatchers.IO) { + private suspend fun selectFidoApplet(): Boolean = withContext(Dispatchers.IO) { try { val response = isoDep.transceive(SELECT_FIDO_APPLET) isSuccess(response) @@ -136,11 +130,31 @@ class NfcTransport(private val isoDep: IsoDep) : FidoTransport { companion object { // FIDO Alliance AID - val SELECT_FIDO_APPLET = byteArrayOf( + private val SELECT_FIDO_APPLET = byteArrayOf( 0x00, 0xA4.toByte(), 0x04, 0x00, // SELECT command 0x08, // Length of AID 0xA0.toByte(), 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01, // FIDO AID 0x00 // Le ) + + /** + * Connect to a FIDO NFC device via the given NFC tag. + * Opens the ISO-DEP connection and selects the FIDO applet. + */ + suspend fun connect(tag: Tag): NfcTransport { + val isoDep = IsoDep.get(tag) ?: throw AuthnkeyError.NotIsoDepTag() + + if (!isoDep.isConnected) { + isoDep.connect() + } + isoDep.timeout = 5000 + + val transport = NfcTransport(isoDep) + if (!transport.selectFidoApplet()) { + transport.close() + throw AuthnkeyError.FidoAppletNotFound() + } + return transport + } } } diff --git a/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt b/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt index 80181e9..34dc48d 100644 --- a/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt +++ b/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt @@ -10,9 +10,7 @@ import kotlin.random.Random /** * FIDO transport over USB HID using CTAPHID protocol */ -class UsbTransport( - private val usbManager: UsbManager, - private val device: UsbDevice, +class UsbTransport private constructor( private val connection: UsbDeviceConnection, private val hidInterface: UsbInterface, private val inEndpoint: UsbEndpoint, @@ -32,7 +30,7 @@ class UsbTransport( /** * Initialize CTAPHID channel */ - suspend fun init(): Boolean = withContext(Dispatchers.IO) { + private suspend fun init(): Boolean = withContext(Dispatchers.IO) { try { // Send INIT command to get a channel val nonce = ByteArray(8).also { Random.nextBytes(it) } @@ -308,25 +306,29 @@ class UsbTransport( } /** - * Create a UsbTransport from a USB device + * Connect to a FIDO USB device. + * Opens the HID interface and initializes the CTAPHID channel. */ - suspend fun create(usbManager: UsbManager, device: UsbDevice): UsbTransport? { - val (hidInterface, endpoints) = findFidoInterface(device) ?: return null + suspend fun connect(usbManager: UsbManager, device: UsbDevice): UsbTransport { + val (hidInterface, endpoints) = findFidoInterface(device) + ?: throw AuthnkeyError.ConnectionFailed() val (inEp, outEp) = endpoints - val connection = usbManager.openDevice(device) ?: return null + val connection = usbManager.openDevice(device) + ?: throw AuthnkeyError.ConnectionFailed() if (!connection.claimInterface(hidInterface, true)) { connection.close() - return null + throw AuthnkeyError.ConnectionFailed() } - val transport = UsbTransport(usbManager, device, connection, hidInterface, inEp, outEp) + val transport = UsbTransport(connection, hidInterface, inEp, outEp) - return if (transport.init()) transport else { + if (!transport.init()) { transport.close() - null + throw AuthnkeyError.ConnectionFailed() } + return transport } } }