Rename getPinToken to requestPinToken and wrap in Result

This commit is contained in:
mimi89999
2025-12-20 22:08:42 +01:00
parent d4993a3633
commit dfb2821cbc
3 changed files with 72 additions and 53 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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<Unit> {
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<Unit> {
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)
}
}