From dfb2821cbced8bfdedf8d452f86e0e2b9fb2e975 Mon Sep 17 00:00:00 2001 From: mimi89999 Date: Sat, 20 Dec 2025 22:08:42 +0100 Subject: [PATCH] Rename getPinToken to requestPinToken and wrap in Result --- .../authnkey/CredentialProviderActivity.kt | 29 ++++---- .../java/pl/lebihan/authnkey/MainActivity.kt | 26 ++++--- .../java/pl/lebihan/authnkey/PinProtocol.kt | 70 +++++++++++-------- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt index e8f30c3..73861ed 100644 --- a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt @@ -530,21 +530,24 @@ class CredentialProviderActivity : AppCompatActivity() { } setInstruction(getString(R.string.instruction_verifying_pin)) - // Try CTAP2.1 style with permissions first - val authenticated = withContext(Dispatchers.IO) { - protocol.getPinToken(pin, permissions, rpId) - } - if (!authenticated) { - val retries = withContext(Dispatchers.IO) { protocol.getPinRetries() }.getOrThrow() - if (retries > 0) { - runOnUiThread { - showProgress(false) - setInstruction(getString(R.string.pin_invalid_retries, retries)) - setState(CredentialBottomSheet.State.PIN) - bottomSheet?.showPinInput(true) + // Try CTAP2.1 style with permissions first (falls back to basic internally) + withContext(Dispatchers.IO) { + protocol.requestPinToken(pin, permissions, rpId) + }.onFailure { e -> + if (e is CTAP.Exception && e.error == CTAP.Error.PIN_INVALID) { + val retries = withContext(Dispatchers.IO) { protocol.getPinRetries() }.getOrThrow() + if (retries > 0) { + runOnUiThread { + showProgress(false) + setInstruction(getString(R.string.pin_invalid_retries, retries)) + setState(CredentialBottomSheet.State.PIN) + bottomSheet?.showPinInput(true) + } + } else { + throw Exception(getString(R.string.error_pin_blocked)) } } else { - throw Exception(getString(R.string.error_pin_blocked)) + throw e } return@launch } diff --git a/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt b/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt index 550f7c1..f39d948 100644 --- a/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/MainActivity.kt @@ -503,17 +503,23 @@ class MainActivity : AppCompatActivity() { } resultText.text = getString(R.string.verifying_pin) - val authenticated = withContext(Dispatchers.IO) { - if (usePreviewCommand) protocol.getPinToken(pin) - else protocol.getPinToken(pin, PinProtocol.PERMISSION_CM) - } - if (!authenticated) { - if (isNfcDisconnected()) { - showNfcReconnectDialog() - } else { - resultText.text = getString(R.string.error_invalid_pin) - pendingAction = null + withContext(Dispatchers.IO) { + if (usePreviewCommand) protocol.requestPinToken(pin) + else protocol.requestPinToken(pin, PinProtocol.PERMISSION_CM) + }.onFailure { e -> + when { + e is java.io.IOException -> throw e + e is CTAP.Exception && e.error == CTAP.Error.PIN_INVALID -> { + resultText.text = getString(R.string.error_invalid_pin) + } + e is CTAP.Exception && e.error == CTAP.Error.PIN_BLOCKED -> { + resultText.text = getString(R.string.error_pin_blocked) + } + else -> { + resultText.text = getString(R.string.error_generic, e.message ?: "") + } } + pendingAction = null return@launch } diff --git a/app/src/main/java/pl/lebihan/authnkey/PinProtocol.kt b/app/src/main/java/pl/lebihan/authnkey/PinProtocol.kt index 3a1cdae..66d6cfc 100644 --- a/app/src/main/java/pl/lebihan/authnkey/PinProtocol.kt +++ b/app/src/main/java/pl/lebihan/authnkey/PinProtocol.kt @@ -56,11 +56,13 @@ class PinProtocol(private val transport: FidoTransport) { } } - suspend fun getPinToken(pin: String): Boolean { - val secret = sharedSecret ?: return false - val pubKey = platformPublicKey ?: return false + suspend fun requestPinToken(pin: String): Result { + val secret = sharedSecret + ?: return Result.failure(Exception("Shared secret not available")) + val pubKey = platformPublicKey + ?: return Result.failure(Exception("Platform key not available")) - try { + return try { val sha256 = MessageDigest.getInstance("SHA-256") val pinHash = sha256.digest(pin.toByteArray(Charsets.UTF_8)) val pinHashLeft16 = pinHash.copyOf(16) @@ -70,24 +72,28 @@ class PinProtocol(private val transport: FidoTransport) { val command = buildGetPinTokenCommand(pubKey, encryptedPinHash) val response = transport.sendCtapCommand(command) - if (!CTAP.isSuccess(response)) { - return false + val error = CTAP.getResponseError(response) + if (error != null) { + return Result.failure(CTAP.Exception(error)) } - val encryptedToken = parsePinTokenResponse(response) ?: return false + val encryptedToken = parsePinTokenResponse(response) + ?: return Result.failure(Exception("Failed to parse PIN token")) pinToken = aesDecrypt(secret, encryptedToken) - return pinToken != null + Result.success(Unit) } catch (e: Exception) { - return false + Result.failure(e) } } - suspend fun getPinToken(pin: String, permissions: Int, rpId: String? = null): Boolean { - val secret = sharedSecret ?: return false - val pubKey = platformPublicKey ?: return false + suspend fun requestPinToken(pin: String, permissions: Int, rpId: String? = null): Result { + val secret = sharedSecret + ?: return Result.failure(Exception("Shared secret not available")) + val pubKey = platformPublicKey + ?: return Result.failure(Exception("Platform key not available")) - try { + return try { val sha256 = MessageDigest.getInstance("SHA-256") val pinHash = sha256.digest(pin.toByteArray(Charsets.UTF_8)) val pinHashLeft16 = pinHash.copyOf(16) @@ -98,30 +104,34 @@ class PinProtocol(private val transport: FidoTransport) { val response = transport.sendCtapCommand(command) if (response.isEmpty()) { - return false - } - - if (CTAP.isSuccess(response)) { - val encryptedToken = parsePinTokenResponse(response) ?: return false - pinToken = aesDecrypt(secret, encryptedToken) - return pinToken != null + return Result.failure(Exception("Empty response")) } val error = CTAP.getResponseError(response) - val fallbackErrors = listOf( - CTAP.Error.INVALID_COMMAND, - CTAP.Error.INVALID_PARAMETER, - CTAP.Error.CBOR_UNEXPECTED_TYPE, - CTAP.Error.MISSING_PARAMETER - ) - if (error in fallbackErrors) { - return getPinToken(pin) + if (error != null) { + // Fallback to basic method if authenticator doesn't support permissions + val fallbackErrors = listOf( + CTAP.Error.INVALID_COMMAND, + CTAP.Error.INVALID_PARAMETER, + CTAP.Error.CBOR_UNEXPECTED_TYPE, + CTAP.Error.MISSING_PARAMETER + ) + if (error in fallbackErrors) { + return requestPinToken(pin) + } + return Result.failure(CTAP.Exception(error)) } - return false + val encryptedToken = parsePinTokenResponse(response) + ?: return Result.failure(Exception("Failed to parse PIN token")) + pinToken = aesDecrypt(secret, encryptedToken) + Result.success(Unit) + } catch (e: java.io.IOException) { + Result.failure(e) } catch (e: Exception) { - return getPinToken(pin) + // On unexpected exception, try fallback to basic method + requestPinToken(pin) } }