Unify NFC and USB transport construction

This commit is contained in:
mimi89999
2026-03-25 23:30:36 +01:00
parent 176d0ae8e4
commit f09dade05a
4 changed files with 46 additions and 36 deletions
@@ -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)
@@ -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)
@@ -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
}
}
}
@@ -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
}
}
}