This commit is contained in:
Adam Velebil 2024-03-26 11:54:43 +01:00
commit 91397d264a
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
15 changed files with 445 additions and 22 deletions

View File

@ -334,7 +334,9 @@ class MainActivity : FlutterFragmentActivity() {
oathViewModel.credentials.streamTo(this, messenger, "android.oath.credentials"), oathViewModel.credentials.streamTo(this, messenger, "android.oath.credentials"),
fidoViewModel.sessionState.streamTo(this, messenger, "android.fido.sessionState"), fidoViewModel.sessionState.streamTo(this, messenger, "android.fido.sessionState"),
fidoViewModel.credentials.streamTo(this, messenger, "android.fido.credentials"), fidoViewModel.credentials.streamTo(this, messenger, "android.fido.credentials"),
fidoViewModel.fingerprints.streamTo(this, messenger, "android.fido.fingerprints"),
fidoViewModel.resetState.streamTo(this, messenger, "android.fido.reset"), fidoViewModel.resetState.streamTo(this, messenger, "android.fido.reset"),
fidoViewModel.registerFingerprint.streamTo(this, messenger, "android.fido.registerFp"),
) )
viewModel.appContext.observe(this) { viewModel.appContext.observe(this) {
@ -348,7 +350,10 @@ class MainActivity : FlutterFragmentActivity() {
// only recreate the contextManager object if it cannot be reused // only recreate the contextManager object if it cannot be reused
if (appContext == OperationContext.Home || if (appContext == OperationContext.Home ||
(appContext == OperationContext.Oath && contextManager is OathManager) || (appContext == OperationContext.Oath && contextManager is OathManager) ||
(appContext == OperationContext.FidoPasskeys && contextManager is FidoManager) (appContext in listOf(
OperationContext.FidoPasskeys,
OperationContext.FidoFingerprints
) && contextManager is FidoManager)
) { ) {
// no need to dispose this context // no need to dispose this context
} else { } else {
@ -367,6 +372,7 @@ class MainActivity : FlutterFragmentActivity() {
appPreferences appPreferences
) )
OperationContext.FidoFingerprints,
OperationContext.FidoPasskeys -> FidoManager( OperationContext.FidoPasskeys -> FidoManager(
messenger, messenger,
deviceManager, deviceManager,

View File

@ -23,7 +23,10 @@ enum class FidoActionDescription(private val value: Int) {
Unlock(1), Unlock(1),
SetPin(2), SetPin(2),
DeleteCredential(3), DeleteCredential(3),
ActionFailure(4); DeleteFingerprint(4),
RenameFingerprint(5),
RegisterFingerprint(6),
ActionFailure(7);
val id: Int val id: Int
get() = value + dialogDescriptionFidoIndex get() = value + dialogDescriptionFidoIndex

View File

@ -16,16 +16,17 @@
package com.yubico.authenticator.fido package com.yubico.authenticator.fido
import android.nfc.TagLostException
import com.yubico.authenticator.AppContextManager import com.yubico.authenticator.AppContextManager
import com.yubico.authenticator.DialogManager import com.yubico.authenticator.DialogManager
import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.MainViewModel
import com.yubico.authenticator.NULL
import com.yubico.authenticator.asString import com.yubico.authenticator.asString
import com.yubico.authenticator.device.DeviceListener import com.yubico.authenticator.device.DeviceListener
import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.device.DeviceManager
import com.yubico.authenticator.device.Info import com.yubico.authenticator.device.Info
import com.yubico.authenticator.device.UnknownDevice import com.yubico.authenticator.device.UnknownDevice
import com.yubico.authenticator.fido.data.FidoCredential import com.yubico.authenticator.fido.data.FidoCredential
import com.yubico.authenticator.fido.data.FidoFingerprint
import com.yubico.authenticator.fido.data.Session import com.yubico.authenticator.fido.data.Session
import com.yubico.authenticator.fido.data.SessionInfo import com.yubico.authenticator.fido.data.SessionInfo
import com.yubico.authenticator.fido.data.YubiKitFidoSession import com.yubico.authenticator.fido.data.YubiKitFidoSession
@ -38,13 +39,17 @@ import com.yubico.yubikit.core.Transport
import com.yubico.yubikit.core.YubiKeyConnection import com.yubico.yubikit.core.YubiKeyConnection
import com.yubico.yubikit.core.YubiKeyDevice import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.application.ApplicationNotAvailableException import com.yubico.yubikit.core.application.ApplicationNotAvailableException
import com.yubico.yubikit.core.application.CommandState
import com.yubico.yubikit.core.fido.CtapException import com.yubico.yubikit.core.fido.CtapException
import com.yubico.yubikit.core.fido.FidoConnection import com.yubico.yubikit.core.fido.FidoConnection
import com.yubico.yubikit.core.internal.Logger
import com.yubico.yubikit.core.smartcard.SmartCardConnection import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.core.util.Result import com.yubico.yubikit.core.util.Result
import com.yubico.yubikit.fido.ctap.BioEnrollment
import com.yubico.yubikit.fido.ctap.ClientPin import com.yubico.yubikit.fido.ctap.ClientPin
import com.yubico.yubikit.fido.ctap.CredentialManagement import com.yubico.yubikit.fido.ctap.CredentialManagement
import com.yubico.yubikit.fido.ctap.Ctap2Session.InfoData import com.yubico.yubikit.fido.ctap.Ctap2Session.InfoData
import com.yubico.yubikit.fido.ctap.FingerprintBioEnrollment
import com.yubico.yubikit.fido.ctap.PinUvAuthDummyProtocol import com.yubico.yubikit.fido.ctap.PinUvAuthDummyProtocol
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol
import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV1 import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV1
@ -72,6 +77,12 @@ class FidoManager(
dialogManager: DialogManager, dialogManager: DialogManager,
) : AppContextManager(), DeviceListener { ) : AppContextManager(), DeviceListener {
@OptIn(ExperimentalStdlibApi::class)
private object HexCodec {
fun bytesToHexString(bytes: ByteArray) : String = bytes.toHexString()
fun hexStringToBytes(hex: String) : ByteArray = hex.hexToByteArray()
}
companion object { companion object {
fun getPreferredPinUvAuthProtocol(infoData: InfoData): PinUvAuthProtocol { fun getPreferredPinUvAuthProtocol(infoData: InfoData): PinUvAuthProtocol {
val pinUvAuthProtocols = infoData.pinUvAuthProtocols val pinUvAuthProtocols = infoData.pinUvAuthProtocols
@ -135,6 +146,21 @@ class FidoManager(
args["credentialId"] as String args["credentialId"] as String
) )
"deleteFingerprint" -> deleteFingerprint(
args["templateId"] as String
)
"renameFingerprint" -> renameFingerprint(
args["templateId"] as String,
args["name"] as String
)
"registerFingerprint" -> registerFingerprint(
args["name"] as String?,
)
"cancelRegisterFingerprint" -> cancelRegisterFingerprint()
else -> throw NotImplementedError() else -> throw NotImplementedError()
} }
} }
@ -215,8 +241,7 @@ class FidoManager(
fidoViewModel.setSessionState( fidoViewModel.setSessionState(
Session( Session(
fidoSession.cachedInfo, fidoSession.cachedInfo, pinStore.hasPin()
pinStore.hasPin()
) )
) )
@ -234,12 +259,14 @@ class FidoManager(
} }
} }
private fun getPermissions(fidoSession: YubiKitFidoSession): Int { private fun getPinPermissionsCM(fidoSession: YubiKitFidoSession): Int {
// TODO: Add bio Enrollment permissions if supported
return if (CredentialManagement.isSupported(fidoSession.cachedInfo)) return if (CredentialManagement.isSupported(fidoSession.cachedInfo))
ClientPin.PIN_PERMISSION_CM ClientPin.PIN_PERMISSION_CM else 0
else }
0
private fun getPinPermissionsBE(fidoSession: YubiKitFidoSession): Int {
return if (BioEnrollment.isSupported(fidoSession.cachedInfo))
ClientPin.PIN_PERMISSION_BE else 0
} }
private fun unlockSession( private fun unlockSession(
@ -250,13 +277,21 @@ class FidoManager(
//fidoViewModel.setSessionLoadingState() //fidoViewModel.setSessionLoadingState()
val permissions = getPermissions(fidoSession) val pinPermissionsCM = getPinPermissionsCM(fidoSession)
val pinPermissionsBE = getPinPermissionsBE(fidoSession)
val permissions = pinPermissionsCM or pinPermissionsBE
if (permissions != 0) { if (permissions != 0) {
val token = clientPin.getPinToken(pin, permissions, null) val token = clientPin.getPinToken(pin, permissions, null)
val credentials = getCredentials(fidoSession, clientPin, token) val credentials = getCredentials(fidoSession, clientPin, token)
logger.debug("Creds: {}", credentials) logger.debug("Creds: {}", credentials)
fidoViewModel.updateCredentials(credentials) fidoViewModel.updateCredentials(credentials)
if (pinPermissionsBE != 0) {
val fingerprints = getFingerprints(fidoSession, clientPin, token)
logger.debug("Fingerprints: {}", fingerprints)
fidoViewModel.updateFingerprints(fingerprints)
}
} else { } else {
clientPin.getPinToken(pin, permissions, "yubico-authenticator.example.com") clientPin.getPinToken(pin, permissions, "yubico-authenticator.example.com")
} }
@ -384,10 +419,8 @@ class FidoManager(
val clientPin = val clientPin =
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)) ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
val permissions = getPermissions(fidoSession) val permissions = getPinPermissionsCM(fidoSession)
val token = clientPin.getPinToken(pinStore.getPin(), permissions, null) val token = clientPin.getPinToken(pinStore.getPin(), permissions, null)
val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, token) val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, token)
val credentialDescriptor = val credentialDescriptor =
@ -413,6 +446,153 @@ class FidoManager(
).toString() ).toString()
} }
private fun getFingerprints(
fidoSession: YubiKitFidoSession,
clientPin: ClientPin,
pinUvAuthToken: ByteArray
): List<FidoFingerprint> {
val bioEnrollment =
FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, pinUvAuthToken)
val enrollments: Map<ByteArray, String?> = bioEnrollment.enumerateEnrollments()
return enrollments.map { enrollment ->
FidoFingerprint(HexCodec.bytesToHexString(enrollment.key), enrollment.value)
}
}
private suspend fun deleteFingerprint(templateId: String): String =
connectionHelper.useSession(FidoActionDescription.DeleteFingerprint) { fidoSession ->
val clientPin =
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
val token =
clientPin.getPinToken(
pinStore.getPin(),
getPinPermissionsBE(fidoSession),
null
)
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
bioEnrollment.removeEnrollment(HexCodec.hexStringToBytes(templateId))
fidoViewModel.removeFingerprint(templateId)
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin()))
return@useSession JSONObject(
mapOf(
"success" to true,
)
).toString()
}
private suspend fun renameFingerprint(templateId: String, name: String): String =
connectionHelper.useSession(FidoActionDescription.RenameFingerprint) { fidoSession ->
val clientPin =
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
val token =
clientPin.getPinToken(
pinStore.getPin(),
getPinPermissionsBE(fidoSession),
null
)
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
bioEnrollment.setName(HexCodec.hexStringToBytes(templateId), name)
fidoViewModel.renameFingerprint(templateId, name)
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin()))
return@useSession JSONObject(
mapOf(
"success" to true,
)
).toString()
}
private var state : CommandState? = null
private fun cancelRegisterFingerprint(): String {
state?.cancel()
return NULL
}
private suspend fun registerFingerprint(name: String?): String =
connectionHelper.useSession(FidoActionDescription.RegisterFingerprint) { fidoSession ->
state?.cancel()
state = CommandState()
val clientPin =
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
val token =
clientPin.getPinToken(
pinStore.getPin(),
getPinPermissionsBE(fidoSession),
null
)
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
val fingerprintEnrollmentContext = bioEnrollment.enroll(null)
var templateId: ByteArray? = null
while (templateId == null) {
try {
templateId = fingerprintEnrollmentContext.capture(state)
fidoViewModel.updateRegisterFpState(
createCaptureEvent(fingerprintEnrollmentContext.remaining!!)
)
} catch (captureError: FingerprintBioEnrollment.CaptureError) {
fidoViewModel.updateRegisterFpState(createCaptureErrorEvent(captureError.code))
} catch (ctapException: CtapException) {
when (ctapException.ctapError) {
CtapException.ERR_KEEPALIVE_CANCEL -> {
fingerprintEnrollmentContext.cancel()
return@useSession JSONObject(
mapOf(
"success" to false,
"status" to "user-cancelled"
)
).toString()
}
CtapException.ERR_USER_ACTION_TIMEOUT -> {
fingerprintEnrollmentContext.cancel()
return@useSession JSONObject(
mapOf(
"success" to false,
"status" to "user-action-timeout"
)
).toString()
}
else -> throw ctapException
}
} catch (io: IOException) {
return@useSession JSONObject(
mapOf(
"success" to false,
"status" to "connection-error"
)
).toString()
}
}
if (!name.isNullOrBlank()) {
bioEnrollment.setName(templateId, name)
Logger.debug(logger, "Set name to {}", name)
}
val templateIdHexString = HexCodec.bytesToHexString(templateId)
fidoViewModel.addFingerprint(FidoFingerprint(templateIdHexString, name))
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin()))
return@useSession JSONObject(
mapOf(
"success" to true,
"template_id" to templateIdHexString,
"name" to name
)
).toString()
}
override fun onDisconnected() { override fun onDisconnected() {
if (!resetHelper.inProgress) { if (!resetHelper.inProgress) {
fidoViewModel.clearSessionState() fidoViewModel.clearSessionState()

View File

@ -29,6 +29,8 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import org.json.JSONObject
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException import java.io.IOException
import java.util.Timer import java.util.Timer
@ -46,6 +48,24 @@ enum class FidoResetState(val value: String) {
Touch("touch") Touch("touch")
} }
@Serializable
sealed class FidoRegisterFpEvent(val status: String)
@Serializable
data class FidoRegisterFpCaptureEvent(private val remaining: Int) : FidoRegisterFpEvent("capture")
@Serializable
data class FidoRegisterFpCaptureErrorEvent(val code: Int) : FidoRegisterFpEvent("capture-error")
fun createCaptureEvent(remaining: Int): FidoRegisterFpCaptureEvent {
return FidoRegisterFpCaptureEvent(remaining)
}
fun createCaptureErrorEvent(code: Int) : FidoRegisterFpCaptureErrorEvent {
return FidoRegisterFpCaptureErrorEvent(code)
}
class FidoResetHelper( class FidoResetHelper(
private val deviceManager: DeviceManager, private val deviceManager: DeviceManager,
private val fidoViewModel: FidoViewModel, private val fidoViewModel: FidoViewModel,

View File

@ -21,7 +21,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.yubico.authenticator.ViewModelData import com.yubico.authenticator.ViewModelData
import com.yubico.authenticator.fido.data.FidoCredential import com.yubico.authenticator.fido.data.FidoCredential
import com.yubico.authenticator.fido.data.FidoFingerprint
import com.yubico.authenticator.fido.data.Session import com.yubico.authenticator.fido.data.Session
import org.json.JSONObject
class FidoViewModel : ViewModel() { class FidoViewModel : ViewModel() {
private val _sessionState = MutableLiveData<ViewModelData>() private val _sessionState = MutableLiveData<ViewModelData>()
@ -60,4 +62,36 @@ class FidoViewModel : ViewModel() {
fun updateResetState(resetState: FidoResetState) { fun updateResetState(resetState: FidoResetState) {
_resetState.postValue(resetState.value) _resetState.postValue(resetState.value)
} }
private val _fingerprints = MutableLiveData<List<FidoFingerprint>>()
val fingerprints: LiveData<List<FidoFingerprint>> = _fingerprints
fun updateFingerprints(fingerprints: List<FidoFingerprint>) {
_fingerprints.postValue(fingerprints)
}
fun addFingerprint(fingerprint: FidoFingerprint) {
_fingerprints.postValue(_fingerprints.value?.plus(fingerprint))
}
fun removeFingerprint(templateId: String) {
_fingerprints.postValue(_fingerprints.value?.filter {
it.templateId != templateId
})
}
fun renameFingerprint(templateId: String, name: String) {
_fingerprints.postValue(_fingerprints.value?.map {
if (it.templateId == templateId) {
FidoFingerprint(templateId, name)
} else it
})
}
private val _registerFingerprint = MutableLiveData<FidoRegisterFpEvent>()
val registerFingerprint: LiveData<FidoRegisterFpEvent> = _registerFingerprint
fun updateRegisterFpState(registerFpState: FidoRegisterFpEvent) {
_registerFingerprint.postValue(registerFpState)
}
} }

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yubico.authenticator.fido.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class FidoFingerprint(
@SerialName("template_id")
val templateId: String,
@SerialName("name")
val name: String?
)

View File

@ -25,6 +25,7 @@ import '../../app/logging.dart';
import '../../app/message.dart'; import '../../app/message.dart';
import '../../app/models.dart'; import '../../app/models.dart';
import '../../app/state.dart'; import '../../app/state.dart';
import '../../desktop/models.dart';
import '../../exception/cancellation_exception.dart'; import '../../exception/cancellation_exception.dart';
import '../../exception/no_data_exception.dart'; import '../../exception/no_data_exception.dart';
import '../../exception/platform_exception_decoder.dart'; import '../../exception/platform_exception_decoder.dart';
@ -165,14 +166,91 @@ final androidFingerprintProvider = AsyncNotifierProvider.autoDispose
_FidoFingerprintsNotifier.new); _FidoFingerprintsNotifier.new);
class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
final _events = const EventChannel('android.fido.fingerprints');
late StreamSubscription _sub;
@override @override
FutureOr<List<Fingerprint>> build(DevicePath devicePath) async { FutureOr<List<Fingerprint>> build(DevicePath devicePath) async {
return []; _sub = _events.receiveBroadcastStream().listen((event) {
final json = jsonDecode(event);
if (json == null) {
state = const AsyncValue.loading();
} else {
List<Fingerprint> newState = List.from(
(json as List).map((e) => Fingerprint.fromJson(e)).toList());
state = AsyncValue.data(newState);
}
}, onError: (err, stackTrace) {
state = AsyncValue.error(err, stackTrace);
});
ref.onDispose(_sub.cancel);
return Completer<List<Fingerprint>>().future;
} }
@override @override
Stream<FingerprintEvent> registerFingerprint({String? name}) { Stream<FingerprintEvent> registerFingerprint({String? name}) {
final controller = StreamController<FingerprintEvent>(); final controller = StreamController<FingerprintEvent>();
const registerEvents = EventChannel('android.fido.registerFp');
final registerFpSub =
registerEvents.receiveBroadcastStream().skip(1).listen((event) {
if (controller.isClosed) {
_log.debug('Controller already closed, ignoring: $event');
}
_log.debug('Received register fingerprint event: $event');
if (event is String && event.isNotEmpty) {
final e = jsonDecode(event);
_log.debug('Received register fingerprint event: $e');
final status = e['status'];
controller.sink.add(switch (status) {
'capture' => FingerprintEvent.capture(e['remaining']),
'capture-error' => FingerprintEvent.error(e['code']),
final other => throw UnimplementedError(other)
});
}
});
controller.onCancel = () async {
if (!controller.isClosed) {
_log.debug('Cancelling fingerprint registration');
await _methods.invokeMethod('cancelRegisterFingerprint');
await registerFpSub.cancel();
}
};
controller.onListen = () async {
try {
final registerFpResult =
await _methods.invokeMethod('registerFingerprint', {'name': name});
_log.debug('Finished registerFingerprint with: $registerFpResult');
final resultJson = jsonDecode(registerFpResult);
if (resultJson['success'] == true) {
controller.sink
.add(FingerprintEvent.complete(Fingerprint.fromJson(resultJson)));
} else {
// TODO abstract platform errors
final errorStatus = resultJson['status'];
if (errorStatus != 'user-cancelled') {
throw RpcError(errorStatus, 'Platform error: $errorStatus', {});
}
}
} on PlatformException catch (pe) {
_log.debug('Received platform exception: \'$pe\'');
final decoded = pe.decode();
controller.sink.addError(decoded);
} catch (e) {
_log.debug('Received error: \'$e\'');
controller.sink.addError(e);
} finally {
await controller.sink.close();
}
};
return controller.stream; return controller.stream;
} }
@ -180,11 +258,60 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
@override @override
Future<Fingerprint> renameFingerprint( Future<Fingerprint> renameFingerprint(
Fingerprint fingerprint, String name) async { Fingerprint fingerprint, String name) async {
return fingerprint; try {
final renameFingerprintResponse = jsonDecode(await _methods.invokeMethod(
'renameFingerprint',
{
'templateId': fingerprint.templateId,
'name': name,
},
));
if (renameFingerprintResponse['success'] == true) {
_log.debug('FIDO rename fingerprint succeeded');
return Fingerprint(fingerprint.templateId, name);
} else {
_log.debug('FIDO rename fingerprint failed');
return fingerprint;
}
} on PlatformException catch (pe) {
var decodedException = pe.decode();
if (decodedException is CancellationException) {
_log.debug('User cancelled rename fingerprint FIDO operation');
} else {
_log.error('Rename fingerprint FIDO operation failed.', pe);
}
throw decodedException;
}
} }
@override @override
Future<void> deleteFingerprint(Fingerprint fingerprint) async {} Future<void> deleteFingerprint(Fingerprint fingerprint) async {
try {
final deleteFingerprintResponse = jsonDecode(await _methods.invokeMethod(
'deleteFingerprint',
{
'templateId': fingerprint.templateId,
},
));
if (deleteFingerprintResponse['success'] == true) {
_log.debug('FIDO delete fingerprint succeeded');
} else {
_log.debug('FIDO delete fingerprint failed');
}
} on PlatformException catch (pe) {
var decodedException = pe.decode();
if (decodedException is CancellationException) {
_log.debug('User cancelled delete fingerprint FIDO operation');
} else {
_log.error('Delete fingerprint FIDO operation failed.', pe);
}
throw decodedException;
}
}
} }
final androidCredentialProvider = AsyncNotifierProvider.autoDispose final androidCredentialProvider = AsyncNotifierProvider.autoDispose

View File

@ -85,12 +85,20 @@ Future<Widget> initialize() async {
), ),
androidSdkVersionProvider.overrideWithValue(await getAndroidSdkVersion()), androidSdkVersionProvider.overrideWithValue(await getAndroidSdkVersion()),
androidNfcSupportProvider.overrideWithValue(await getHasNfc()), androidNfcSupportProvider.overrideWithValue(await getHasNfc()),
supportedSectionsProvider.overrideWithValue( supportedSectionsProvider.overrideWithValue([
[Section.home, Section.accounts, Section.passkeys]), Section.home,
Section.accounts,
Section.fingerprints,
Section.passkeys
]),
// this specifies the priority of sections to show when // this specifies the priority of sections to show when
// the connected YubiKey does not support current section // the connected YubiKey does not support current section
androidSectionPriority.overrideWithValue( androidSectionPriority.overrideWithValue([
[Section.accounts, Section.passkeys, Section.home]), Section.accounts,
Section.fingerprints,
Section.passkeys,
Section.home
]),
supportedThemesProvider.overrideWith( supportedThemesProvider.overrideWith(
(ref) => ref.watch(androidSupportedThemesProvider), (ref) => ref.watch(androidSupportedThemesProvider),
), ),

View File

@ -78,6 +78,8 @@ enum _DDesc {
fidoUnlockSession, fidoUnlockSession,
fidoSetPin, fidoSetPin,
fidoDeleteCredential, fidoDeleteCredential,
fidoDeleteFingerprint,
fidoRenameFingerprint,
fidoActionFailure, fidoActionFailure,
// Others // Others
invalid; invalid;
@ -101,7 +103,9 @@ enum _DDesc {
dialogDescriptionFidoIndex + 1: fidoUnlockSession, dialogDescriptionFidoIndex + 1: fidoUnlockSession,
dialogDescriptionFidoIndex + 2: fidoSetPin, dialogDescriptionFidoIndex + 2: fidoSetPin,
dialogDescriptionFidoIndex + 3: fidoDeleteCredential, dialogDescriptionFidoIndex + 3: fidoDeleteCredential,
dialogDescriptionFidoIndex + 4: fidoActionFailure, dialogDescriptionFidoIndex + 4: fidoDeleteFingerprint,
dialogDescriptionFidoIndex + 5: fidoRenameFingerprint,
dialogDescriptionFidoIndex + 6: fidoActionFailure,
}[id] ?? }[id] ??
_DDesc.invalid; _DDesc.invalid;
} }
@ -179,6 +183,8 @@ class _DialogProvider {
_DDesc.fidoUnlockSession => l10n.s_nfc_dialog_fido_unlock, _DDesc.fidoUnlockSession => l10n.s_nfc_dialog_fido_unlock,
_DDesc.fidoSetPin => l10n.l_nfc_dialog_fido_set_pin, _DDesc.fidoSetPin => l10n.l_nfc_dialog_fido_set_pin,
_DDesc.fidoDeleteCredential => l10n.s_nfc_dialog_fido_delete_credential, _DDesc.fidoDeleteCredential => l10n.s_nfc_dialog_fido_delete_credential,
_DDesc.fidoDeleteFingerprint => l10n.s_nfc_dialog_fido_delete_fingerprint,
_DDesc.fidoRenameFingerprint => l10n.s_nfc_dialog_fido_rename_fingerprint,
_DDesc.fidoActionFailure => l10n.s_nfc_dialog_fido_failure, _DDesc.fidoActionFailure => l10n.s_nfc_dialog_fido_failure,
_ => '' _ => ''
}; };

View File

@ -63,6 +63,7 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
void dispose() { void dispose() {
_animator.dispose(); _animator.dispose();
_nameFocus.dispose(); _nameFocus.dispose();
_subscription.cancel();
super.dispose(); super.dispose();
} }

View File

@ -838,6 +838,8 @@
"s_nfc_dialog_fido_unlock": null, "s_nfc_dialog_fido_unlock": null,
"l_nfc_dialog_fido_set_pin": null, "l_nfc_dialog_fido_set_pin": null,
"s_nfc_dialog_fido_delete_credential": null, "s_nfc_dialog_fido_delete_credential": null,
"s_nfc_dialog_fido_delete_fingerprint": null,
"s_nfc_dialog_fido_rename_fingerprint": null,
"s_nfc_dialog_fido_failure": null, "s_nfc_dialog_fido_failure": null,
"@_ndef": {}, "@_ndef": {},

View File

@ -838,6 +838,8 @@
"s_nfc_dialog_fido_unlock": "Action: unlock FIDO applet", "s_nfc_dialog_fido_unlock": "Action: unlock FIDO applet",
"l_nfc_dialog_fido_set_pin": "Action: set or change FIDO applet PIN", "l_nfc_dialog_fido_set_pin": "Action: set or change FIDO applet PIN",
"s_nfc_dialog_fido_delete_credential": "Action: delete Passkey", "s_nfc_dialog_fido_delete_credential": "Action: delete Passkey",
"s_nfc_dialog_fido_delete_fingerprint": "Action: delete fingerprint",
"s_nfc_dialog_fido_rename_fingerprint": "Action: rename fingerprint",
"s_nfc_dialog_fido_failure": "FIDO operation failed", "s_nfc_dialog_fido_failure": "FIDO operation failed",
"@_ndef": {}, "@_ndef": {},

View File

@ -838,6 +838,8 @@
"s_nfc_dialog_fido_unlock": null, "s_nfc_dialog_fido_unlock": null,
"l_nfc_dialog_fido_set_pin": null, "l_nfc_dialog_fido_set_pin": null,
"s_nfc_dialog_fido_delete_credential": null, "s_nfc_dialog_fido_delete_credential": null,
"s_nfc_dialog_fido_delete_fingerprint": null,
"s_nfc_dialog_fido_rename_fingerprint": null,
"s_nfc_dialog_fido_failure": null, "s_nfc_dialog_fido_failure": null,
"@_ndef": {}, "@_ndef": {},

View File

@ -838,6 +838,8 @@
"s_nfc_dialog_fido_unlock": null, "s_nfc_dialog_fido_unlock": null,
"l_nfc_dialog_fido_set_pin": null, "l_nfc_dialog_fido_set_pin": null,
"s_nfc_dialog_fido_delete_credential": null, "s_nfc_dialog_fido_delete_credential": null,
"s_nfc_dialog_fido_delete_fingerprint": null,
"s_nfc_dialog_fido_rename_fingerprint": null,
"s_nfc_dialog_fido_failure": null, "s_nfc_dialog_fido_failure": null,
"@_ndef": {}, "@_ndef": {},

View File

@ -838,6 +838,8 @@
"s_nfc_dialog_fido_unlock": null, "s_nfc_dialog_fido_unlock": null,
"l_nfc_dialog_fido_set_pin": null, "l_nfc_dialog_fido_set_pin": null,
"s_nfc_dialog_fido_delete_credential": null, "s_nfc_dialog_fido_delete_credential": null,
"s_nfc_dialog_fido_delete_fingerprint": null,
"s_nfc_dialog_fido_rename_fingerprint": null,
"s_nfc_dialog_fido_failure": null, "s_nfc_dialog_fido_failure": null,
"@_ndef": {}, "@_ndef": {},