mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
Merge branch main into adamve/nfc_activity_widget
This commit is contained in:
commit
32d9cb1b39
@ -55,7 +55,7 @@ import com.yubico.authenticator.oath.OathViewModel
|
||||
import com.yubico.authenticator.yubikit.NfcActivityDispatcher
|
||||
import com.yubico.authenticator.yubikit.NfcActivityListener
|
||||
import com.yubico.authenticator.yubikit.NfcActivityState
|
||||
import com.yubico.authenticator.yubikit.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
import com.yubico.yubikit.android.YubiKitManager
|
||||
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
|
||||
@ -67,7 +67,6 @@ import com.yubico.yubikit.android.transport.usb.UsbYubiKeyManager
|
||||
import com.yubico.yubikit.core.Transport
|
||||
import com.yubico.yubikit.core.YubiKeyDevice
|
||||
import com.yubico.yubikit.core.smartcard.SmartCardConnection
|
||||
import com.yubico.yubikit.core.smartcard.scp.KeyRef
|
||||
import com.yubico.yubikit.core.smartcard.scp.Scp11KeyParams
|
||||
import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams
|
||||
import com.yubico.yubikit.core.smartcard.scp.ScpKid
|
||||
@ -152,7 +151,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
}
|
||||
|
||||
hasNfc = true
|
||||
} catch (e: NfcNotAvailable) {
|
||||
} catch (_: NfcNotAvailable) {
|
||||
hasNfc = false
|
||||
}
|
||||
|
||||
@ -348,7 +347,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
switchContext(preferredContext)
|
||||
}
|
||||
|
||||
if (contextManager == null) {
|
||||
if (contextManager == null && supportedContexts.isNotEmpty()) {
|
||||
switchContext(DeviceManager.getPreferredContext(supportedContexts))
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2023-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.device
|
||||
|
||||
import com.yubico.yubikit.core.Transport
|
||||
@ -17,7 +33,7 @@ val UnknownDevice = Info(
|
||||
isLocked = false,
|
||||
isSky = false,
|
||||
isFips = false,
|
||||
name = "Unrecognized device",
|
||||
name = "unknown-device",
|
||||
isNfc = false,
|
||||
usbPid = null,
|
||||
pinComplexity = false,
|
||||
@ -50,4 +66,15 @@ fun unknownFido2DeviceInfo(transport: Transport) : Info {
|
||||
return unknownDeviceWithCapability(transport, Capability.FIDO2.bit).copy(
|
||||
name = "FIDO2 device"
|
||||
)
|
||||
}
|
||||
|
||||
fun restrictedNfcDeviceInfo(transport: Transport) : Info {
|
||||
if (transport != Transport.NFC) {
|
||||
return UnknownDevice
|
||||
}
|
||||
|
||||
return UnknownDevice.copy(
|
||||
isNfc = true,
|
||||
name = "restricted-nfc"
|
||||
)
|
||||
}
|
@ -44,7 +44,7 @@ import com.yubico.authenticator.oath.keystore.KeyStoreProvider
|
||||
import com.yubico.authenticator.oath.keystore.SharedPrefProvider
|
||||
import com.yubico.authenticator.yubikit.NfcActivityListener
|
||||
import com.yubico.authenticator.yubikit.NfcActivityState
|
||||
import com.yubico.authenticator.yubikit.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
|
||||
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
||||
|
@ -16,8 +16,9 @@
|
||||
|
||||
package com.yubico.authenticator.yubikit
|
||||
|
||||
import com.yubico.authenticator.device.Info
|
||||
import com.yubico.authenticator.compatUtil
|
||||
import com.yubico.authenticator.device.Info
|
||||
import com.yubico.authenticator.device.restrictedNfcDeviceInfo
|
||||
import com.yubico.authenticator.device.unknownDeviceWithCapability
|
||||
import com.yubico.authenticator.device.unknownFido2DeviceInfo
|
||||
import com.yubico.authenticator.device.unknownOathDeviceInfo
|
||||
@ -27,58 +28,98 @@ import com.yubico.yubikit.core.YubiKeyDevice
|
||||
import com.yubico.yubikit.core.application.ApplicationNotAvailableException
|
||||
import com.yubico.yubikit.core.fido.FidoConnection
|
||||
import com.yubico.yubikit.core.otp.OtpConnection
|
||||
import com.yubico.yubikit.core.smartcard.Apdu
|
||||
import com.yubico.yubikit.core.smartcard.SmartCardConnection
|
||||
import com.yubico.yubikit.core.smartcard.SmartCardProtocol
|
||||
import com.yubico.yubikit.fido.ctap.Ctap2Session
|
||||
import com.yubico.yubikit.management.DeviceInfo
|
||||
import com.yubico.yubikit.oath.OathSession
|
||||
import com.yubico.yubikit.support.DeviceUtil
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
suspend fun getDeviceInfo(device: YubiKeyDevice): Info? {
|
||||
val pid = (device as? UsbYubiKeyDevice)?.pid
|
||||
val logger = LoggerFactory.getLogger("getDeviceInfo")
|
||||
class DeviceInfoHelper {
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger("DeviceInfoHelper")
|
||||
private val nfcTagReaderAid = byteArrayOf(0xD2.toByte(), 0x76, 0, 0, 0x85.toByte(), 1, 1)
|
||||
private val uri = "yubico.com/getting-started".toByteArray()
|
||||
private val restrictedNfcBytes =
|
||||
byteArrayOf(0x00, 0x1F, 0xD1.toByte(), 0x01, 0x1b, 0x55, 0x04) + uri
|
||||
|
||||
val deviceInfo = runCatching {
|
||||
device.withConnection<SmartCardConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) }
|
||||
}.recoverCatching { t ->
|
||||
logger.debug("Smart card connection not available: {}", t.message)
|
||||
device.withConnection<OtpConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) }
|
||||
}.recoverCatching { t ->
|
||||
logger.debug("OTP connection not available: {}", t.message)
|
||||
device.withConnection<FidoConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) }
|
||||
}.recoverCatching { t ->
|
||||
logger.debug("FIDO connection not available: {}", t.message)
|
||||
return SkyHelper(compatUtil).getDeviceInfo(device)
|
||||
}.getOrElse {
|
||||
// this is not a YubiKey
|
||||
logger.debug("Probing unknown device")
|
||||
try {
|
||||
device.openConnection(SmartCardConnection::class.java).use { smartCardConnection ->
|
||||
suspend fun getDeviceInfo(device: YubiKeyDevice): Info? {
|
||||
val pid = (device as? UsbYubiKeyDevice)?.pid
|
||||
|
||||
|
||||
val deviceInfo = runCatching {
|
||||
device.withConnection<SmartCardConnection, DeviceInfo> {
|
||||
DeviceUtil.readInfo(
|
||||
it,
|
||||
pid
|
||||
)
|
||||
}
|
||||
}.recoverCatching { t ->
|
||||
logger.debug("Smart card connection not available: {}", t.message)
|
||||
device.withConnection<OtpConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) }
|
||||
}.recoverCatching { t ->
|
||||
logger.debug("OTP connection not available: {}", t.message)
|
||||
device.withConnection<FidoConnection, DeviceInfo> { DeviceUtil.readInfo(it, pid) }
|
||||
}.recoverCatching { t ->
|
||||
logger.debug("FIDO connection not available: {}", t.message)
|
||||
return SkyHelper(compatUtil).getDeviceInfo(device)
|
||||
}.getOrElse {
|
||||
// this is not a YubiKey
|
||||
logger.debug("Probing unknown device")
|
||||
try {
|
||||
// if OATH session is available use it
|
||||
OathSession(smartCardConnection)
|
||||
logger.debug("Device supports OATH")
|
||||
return unknownOathDeviceInfo(device.transport)
|
||||
} catch (applicationNotAvailable: ApplicationNotAvailableException) {
|
||||
try {
|
||||
// probe for CTAP2 availability
|
||||
Ctap2Session(smartCardConnection)
|
||||
logger.debug("Device supports FIDO2")
|
||||
return unknownFido2DeviceInfo(device.transport)
|
||||
} catch (applicationNotAvailable: ApplicationNotAvailableException) {
|
||||
logger.debug("Device not recognized")
|
||||
return unknownDeviceWithCapability(device.transport)
|
||||
}
|
||||
device.openConnection(SmartCardConnection::class.java)
|
||||
.use { smartCardConnection ->
|
||||
try {
|
||||
// if OATH session is available use it
|
||||
OathSession(smartCardConnection)
|
||||
logger.debug("Device supports OATH")
|
||||
return unknownOathDeviceInfo(device.transport)
|
||||
} catch (_: ApplicationNotAvailableException) {
|
||||
try {
|
||||
// probe for CTAP2 availability
|
||||
Ctap2Session(smartCardConnection)
|
||||
logger.debug("Device supports FIDO2")
|
||||
return unknownFido2DeviceInfo(device.transport)
|
||||
} catch (_: ApplicationNotAvailableException) {
|
||||
// probe for NFC restricted device
|
||||
if (isNfcRestricted(smartCardConnection)) {
|
||||
logger.debug("Device has restricted NFC")
|
||||
return restrictedNfcDeviceInfo(device.transport)
|
||||
}
|
||||
logger.debug("Device not recognized")
|
||||
return unknownDeviceWithCapability(device.transport)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// no smart card connectivity
|
||||
logger.error("Failure getting device info", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val name = DeviceUtil.getName(deviceInfo, pid?.type)
|
||||
return Info(name, device is NfcYubiKeyDevice, pid?.value, deviceInfo)
|
||||
}
|
||||
|
||||
private fun isNfcRestricted(connection: SmartCardConnection): Boolean =
|
||||
restrictedNfcBytes.contentEquals(readNdef(connection).also {
|
||||
logger.debug("ndef: {}", it)
|
||||
})
|
||||
|
||||
private fun readNdef(connection: SmartCardConnection): ByteArray? = try {
|
||||
with(SmartCardProtocol(connection)) {
|
||||
select(nfcTagReaderAid)
|
||||
sendAndReceive(Apdu(0x00, 0xA4, 0x00, 0x0C, byteArrayOf(0xE1.toByte(), 0x04)))
|
||||
sendAndReceive(Apdu(0x00, 0xB0, 0x00, 0x00, null))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// no smart card connectivity
|
||||
logger.error("Failure getting device info", e)
|
||||
return null
|
||||
logger.debug("Failed to read ndef tag: ", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val name = DeviceUtil.getName(deviceInfo, pid?.type)
|
||||
return Info(name, device is NfcYubiKeyDevice, pid?.value, deviceInfo)
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,13 @@ from ykman.device import scan_devices, list_all_devices
|
||||
from ykman.diagnostics import get_diagnostics
|
||||
from ykman.logging import set_log_level
|
||||
from yubikit.core import TRANSPORT, NotSupportedError
|
||||
from yubikit.core.smartcard import SmartCardConnection, ApduError, SW
|
||||
from yubikit.core.smartcard import (
|
||||
SmartCardConnection,
|
||||
ApduError,
|
||||
SW,
|
||||
SmartCardProtocol,
|
||||
ApplicationNotAvailableError,
|
||||
)
|
||||
from yubikit.core.smartcard.scp import Scp11KeyParams
|
||||
from yubikit.core.otp import OtpConnection
|
||||
from yubikit.core.fido import FidoConnection
|
||||
@ -376,6 +382,9 @@ class _ReaderObserver(CardObserver):
|
||||
logger.debug(f"NFC card: {self.card}")
|
||||
|
||||
|
||||
RESTRICTED_NDEF = bytes.fromhex("001fd1011b5504") + b"yubico.com/getting-started"
|
||||
|
||||
|
||||
class ReaderDeviceNode(AbstractDeviceNode):
|
||||
def __init__(self, device, info):
|
||||
super().__init__(device, info)
|
||||
@ -398,14 +407,27 @@ class ReaderDeviceNode(AbstractDeviceNode):
|
||||
return dict(present=False, status="no-card")
|
||||
try:
|
||||
with self._device.open_connection(SmartCardConnection) as conn:
|
||||
data = dict(self._read_data(conn), present=True)
|
||||
try:
|
||||
data = dict(self._read_data(conn), present=True)
|
||||
except ValueError:
|
||||
# Unknown device, maybe NFC restricted
|
||||
try:
|
||||
p = SmartCardProtocol(conn)
|
||||
p.select(bytes.fromhex("D2760000850101"))
|
||||
p.send_apdu(0, 0xA4, 0x00, 0x0C, bytes.fromhex("E104"))
|
||||
ndef = p.send_apdu(0, 0xB0, 0, 0)
|
||||
except (ApduError, ApplicationNotAvailableError):
|
||||
ndef = None
|
||||
|
||||
if ndef == RESTRICTED_NDEF:
|
||||
data = dict(present=False, status="restricted-nfc")
|
||||
else:
|
||||
data = dict(present=False, status="unknown-device")
|
||||
|
||||
self._observer.needs_refresh = False
|
||||
return data
|
||||
except NoCardException:
|
||||
return dict(present=False, status="no-card")
|
||||
except ValueError:
|
||||
self._observer.needs_refresh = False
|
||||
return dict(present=False, status="unknown-device")
|
||||
|
||||
@action(closes_child=False)
|
||||
def get(self, params, event, signal):
|
||||
|
@ -186,8 +186,18 @@ class AndroidAttachedDevicesNotifier extends AttachedDevicesNotifier {
|
||||
.maybeWhen(data: (data) => [data.node], orElse: () => []);
|
||||
}
|
||||
|
||||
final androidDeviceDataProvider = Provider<AsyncValue<YubiKeyData>>(
|
||||
(ref) => ref.watch(androidYubikeyProvider));
|
||||
final androidDeviceDataProvider = Provider<AsyncValue<YubiKeyData>>((ref) {
|
||||
return ref.watch(androidYubikeyProvider).when(data: (d) {
|
||||
if (d.name == 'restricted-nfc' || d.name == 'unknown-device') {
|
||||
return AsyncError(d.name, StackTrace.current);
|
||||
}
|
||||
return AsyncData(d);
|
||||
}, error: (Object error, StackTrace stackTrace) {
|
||||
return AsyncError(error, stackTrace);
|
||||
}, loading: () {
|
||||
return const AsyncLoading();
|
||||
});
|
||||
});
|
||||
|
||||
class AndroidCurrentDeviceNotifier extends CurrentDeviceNotifier {
|
||||
@override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2024 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -78,6 +78,17 @@ class DeviceErrorScreen extends ConsumerWidget {
|
||||
),
|
||||
header: l10n.s_unknown_device,
|
||||
),
|
||||
'restricted-nfc' => HomeMessagePage(
|
||||
centered: true,
|
||||
graphic: Icon(
|
||||
Symbols.contactless,
|
||||
size: 96,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
header: l10n.l_deactivate_restricted_nfc,
|
||||
message: l10n.p_deactivate_restricted_nfc_desc,
|
||||
footnote: l10n.p_deactivate_restricted_nfc_footer,
|
||||
),
|
||||
_ => HomeMessagePage(
|
||||
centered: true,
|
||||
graphic: Image.asset(
|
||||
|
@ -155,6 +155,7 @@ List<String> _getDeviceStrings(
|
||||
error: (error, _) => switch (error) {
|
||||
'device-inaccessible' => [node.name, l10n.s_yk_inaccessible],
|
||||
'unknown-device' => [l10n.s_unknown_device],
|
||||
'restricted-nfc' => [l10n.s_restricted_nfc],
|
||||
_ => null,
|
||||
},
|
||||
) ??
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
* Copyright (C) 2022-2024 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -120,19 +120,7 @@ class MainPage extends ConsumerWidget {
|
||||
data: (data) {
|
||||
final section = ref.watch(currentSectionProvider);
|
||||
final capabilities = section.capabilities;
|
||||
if (data.info.supportedCapabilities.isEmpty &&
|
||||
data.name == 'Unrecognized device') {
|
||||
return HomeMessagePage(
|
||||
centered: true,
|
||||
graphic: Icon(
|
||||
Symbols.help,
|
||||
size: 96,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
header: l10n.s_yk_not_recognized,
|
||||
);
|
||||
} else if (section.getAvailability(data) ==
|
||||
Availability.unsupported) {
|
||||
if (section.getAvailability(data) == Availability.unsupported) {
|
||||
return MessagePage(
|
||||
title: section.getDisplayName(l10n),
|
||||
capabilities: capabilities,
|
||||
|
@ -76,7 +76,7 @@ class MessagePage extends StatelessWidget {
|
||||
right: 18.0,
|
||||
bottom: centered && actionsBuilder == null ? 96 : 0),
|
||||
child: SizedBox(
|
||||
width: centered ? 250 : 350,
|
||||
width: 350,
|
||||
child: Column(
|
||||
crossAxisAlignment: centered
|
||||
? CrossAxisAlignment.center
|
||||
@ -93,7 +93,7 @@ class MessagePage extends StatelessWidget {
|
||||
if (message != null) ...[
|
||||
const SizedBox(height: 12.0),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
constraints: const BoxConstraints(maxWidth: 350),
|
||||
child: Text(message!,
|
||||
textAlign: centered ? TextAlign.center : TextAlign.left,
|
||||
style: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
|
@ -72,6 +72,7 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
StreamSubscription<InteractionEvent>? _subscription;
|
||||
InteractionEvent? _interaction;
|
||||
int _currentStep = -1;
|
||||
bool _resetting = false;
|
||||
late final int _totalSteps;
|
||||
|
||||
@override
|
||||
@ -125,6 +126,7 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
return ResponsiveDialog(
|
||||
title: Text(l10n.s_factory_reset),
|
||||
key: factoryResetCancel,
|
||||
allowCancel: !_resetting || _application == Capability.fido2,
|
||||
onCancel: switch (_application) {
|
||||
Capability.fido2 => _currentStep < _totalSteps
|
||||
? () {
|
||||
@ -144,83 +146,97 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
actions: [
|
||||
if (_currentStep < _totalSteps)
|
||||
TextButton(
|
||||
onPressed: switch (_application) {
|
||||
Capability.fido2 => _subscription == null
|
||||
? () async {
|
||||
_subscription = ref
|
||||
.read(
|
||||
fidoStateProvider(widget.data.node.path).notifier)
|
||||
.reset()
|
||||
.listen((event) {
|
||||
setState(() {
|
||||
_currentStep++;
|
||||
_interaction = event;
|
||||
});
|
||||
}, onDone: () {
|
||||
setState(() {
|
||||
_currentStep++;
|
||||
});
|
||||
_subscription = null;
|
||||
}, onError: (e) {
|
||||
_log.error('Error performing FIDO reset', e);
|
||||
onPressed: !_resetting
|
||||
? switch (_application) {
|
||||
Capability.fido2 => () async {
|
||||
_subscription = ref
|
||||
.read(fidoStateProvider(widget.data.node.path)
|
||||
.notifier)
|
||||
.reset()
|
||||
.listen((event) {
|
||||
setState(() {
|
||||
_resetting = true;
|
||||
_currentStep++;
|
||||
_interaction = event;
|
||||
});
|
||||
}, onDone: () {
|
||||
setState(() {
|
||||
_currentStep++;
|
||||
});
|
||||
_subscription = null;
|
||||
}, onError: (e) {
|
||||
_log.error('Error performing FIDO reset', e);
|
||||
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
final String errorMessage;
|
||||
// TODO: Make this cleaner than importing desktop specific RpcError.
|
||||
if (e is RpcError) {
|
||||
if (e.status == 'connection-error') {
|
||||
errorMessage = l10n.l_failed_connecting_to_fido;
|
||||
} else if (e.status == 'key-mismatch') {
|
||||
errorMessage = l10n.l_wrong_inserted_yk_error;
|
||||
} else if (e.status == 'user-action-timeout') {
|
||||
errorMessage = l10n.l_user_action_timeout_error;
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
final String errorMessage;
|
||||
// TODO: Make this cleaner than importing desktop specific RpcError.
|
||||
if (e is RpcError) {
|
||||
if (e.status == 'connection-error') {
|
||||
errorMessage = l10n.l_failed_connecting_to_fido;
|
||||
} else if (e.status == 'key-mismatch') {
|
||||
errorMessage = l10n.l_wrong_inserted_yk_error;
|
||||
} else if (e.status == 'user-action-timeout') {
|
||||
errorMessage = l10n.l_user_action_timeout_error;
|
||||
} else {
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else {
|
||||
errorMessage = e.message;
|
||||
errorMessage = e.toString();
|
||||
}
|
||||
} else {
|
||||
errorMessage = e.toString();
|
||||
}
|
||||
showMessage(
|
||||
context,
|
||||
l10n.l_reset_failed(errorMessage),
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
});
|
||||
}
|
||||
: null,
|
||||
Capability.oath => () async {
|
||||
await ref
|
||||
.read(oathStateProvider(widget.data.node.path).notifier)
|
||||
.reset();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.l_oath_application_reset);
|
||||
});
|
||||
},
|
||||
Capability.piv => () async {
|
||||
await ref
|
||||
.read(pivStateProvider(widget.data.node.path).notifier)
|
||||
.reset();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.l_piv_app_reset);
|
||||
});
|
||||
},
|
||||
null => globalReset
|
||||
? () async {
|
||||
await ref
|
||||
.read(managementStateProvider(widget.data.node.path)
|
||||
.notifier)
|
||||
.deviceReset();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.s_factory_reset);
|
||||
});
|
||||
}
|
||||
: null,
|
||||
_ => throw UnsupportedError('Application cannot be reset'),
|
||||
},
|
||||
showMessage(
|
||||
context,
|
||||
l10n.l_reset_failed(errorMessage),
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
});
|
||||
},
|
||||
Capability.oath => () async {
|
||||
setState(() {
|
||||
_resetting = true;
|
||||
});
|
||||
await ref
|
||||
.read(oathStateProvider(widget.data.node.path)
|
||||
.notifier)
|
||||
.reset();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.l_oath_application_reset);
|
||||
});
|
||||
},
|
||||
Capability.piv => () async {
|
||||
setState(() {
|
||||
_resetting = true;
|
||||
});
|
||||
await ref
|
||||
.read(pivStateProvider(widget.data.node.path)
|
||||
.notifier)
|
||||
.reset();
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.l_piv_app_reset);
|
||||
});
|
||||
},
|
||||
null => globalReset
|
||||
? () async {
|
||||
setState(() {
|
||||
_resetting = true;
|
||||
});
|
||||
await ref
|
||||
.read(managementStateProvider(
|
||||
widget.data.node.path)
|
||||
.notifier)
|
||||
.deviceReset();
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.s_factory_reset);
|
||||
});
|
||||
}
|
||||
: null,
|
||||
_ => throw UnsupportedError('Application cannot be reset'),
|
||||
}
|
||||
: null,
|
||||
key: factoryResetReset,
|
||||
child: Text(l10n.s_reset),
|
||||
)
|
||||
@ -306,10 +322,12 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
|
||||
},
|
||||
),
|
||||
],
|
||||
if (_application == Capability.fido2 && _currentStep >= 0) ...[
|
||||
Text('${l10n.s_status}: ${_getMessage()}'),
|
||||
LinearProgressIndicator(value: progress)
|
||||
],
|
||||
if (_resetting)
|
||||
if (_application == Capability.fido2 && _currentStep >= 0) ...[
|
||||
Text('${l10n.s_status}: ${_getMessage()}'),
|
||||
LinearProgressIndicator(value: progress),
|
||||
] else
|
||||
const LinearProgressIndicator()
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
|
@ -229,6 +229,10 @@
|
||||
"l_no_yk_present": "Kein YubiKey vorhanden",
|
||||
"s_unknown_type": "Unbekannter Typ",
|
||||
"s_unknown_device": "Unbekanntes Gerät",
|
||||
"s_restricted_nfc": null,
|
||||
"l_deactivate_restricted_nfc": null,
|
||||
"p_deactivate_restricted_nfc_desc": null,
|
||||
"p_deactivate_restricted_nfc_footer": null,
|
||||
"s_unsupported_yk": "Nicht unterstützter YubiKey",
|
||||
"s_yk_not_recognized": "Geräte nicht erkannt",
|
||||
"p_operation_failed_try_again": "Die Aktion ist fehlgeschlagen, bitte versuchen Sie es erneut.",
|
||||
@ -500,6 +504,7 @@
|
||||
}
|
||||
},
|
||||
"@_fingerprints": {},
|
||||
"s_biometrics": null,
|
||||
"l_fingerprint": "Fingerabdruck: {label}",
|
||||
"@l_fingerprint": {
|
||||
"placeholders": {
|
||||
@ -660,6 +665,7 @@
|
||||
"s_allow_fingerprint": null,
|
||||
"p_cert_options_desc": "Verwendeter Schlüssel-Algorithmus, Ausgabeformat und Ablaufdatum (nur Zertifikat).",
|
||||
"p_cert_options_bio_desc": null,
|
||||
"p_key_options_bio_desc": null,
|
||||
"s_overwrite_slot": "Slot überschreiben",
|
||||
"p_overwrite_slot_desc": "Damit wird vorhandener Inhalt im Slot {slot} dauerhaft überschrieben.",
|
||||
"@p_overwrite_slot_desc": {
|
||||
|
@ -229,6 +229,10 @@
|
||||
"l_no_yk_present": "No YubiKey present",
|
||||
"s_unknown_type": "Unknown type",
|
||||
"s_unknown_device": "Unrecognized device",
|
||||
"s_restricted_nfc": "NFC activation",
|
||||
"l_deactivate_restricted_nfc": "How to activate NFC",
|
||||
"p_deactivate_restricted_nfc_desc": "Connect your YubiKey to any USB power source, such as a computer, for at least 3 seconds.\n\nOnce powered, NFC will be activated and ready for use.",
|
||||
"p_deactivate_restricted_nfc_footer": "Your YubiKey is equipped with Restricted NFC, a feature designed to safeguard against wireless manipulation during shipping. This means that NFC operations are temporarily disabled until you activate them.",
|
||||
"s_unsupported_yk": "Unsupported YubiKey",
|
||||
"s_yk_not_recognized": "Device not recognized",
|
||||
"p_operation_failed_try_again": "The operation failed, please try again.",
|
||||
@ -500,6 +504,7 @@
|
||||
}
|
||||
},
|
||||
"@_fingerprints": {},
|
||||
"s_biometrics": "Biometrics",
|
||||
"l_fingerprint": "Fingerprint: {label}",
|
||||
"@l_fingerprint": {
|
||||
"placeholders": {
|
||||
@ -660,6 +665,7 @@
|
||||
"s_allow_fingerprint": "Allow fingerprint",
|
||||
"p_cert_options_desc": "Key algorithm to use, output format, and expiration date (certificate only).",
|
||||
"p_cert_options_bio_desc": "Key algorithm to use, output format, expiration date (certificate only), and if biometrics can be used instead of PIN.",
|
||||
"p_key_options_bio_desc": "Allow biometrics to be used instead of PIN.",
|
||||
"s_overwrite_slot": "Overwrite slot",
|
||||
"p_overwrite_slot_desc": "This will permanently overwrite existing content in slot {slot}.",
|
||||
"@p_overwrite_slot_desc": {
|
||||
|
@ -229,6 +229,10 @@
|
||||
"l_no_yk_present": "Aucune YubiKey présente",
|
||||
"s_unknown_type": "Type inconnu",
|
||||
"s_unknown_device": "Appareil non reconnu",
|
||||
"s_restricted_nfc": null,
|
||||
"l_deactivate_restricted_nfc": null,
|
||||
"p_deactivate_restricted_nfc_desc": null,
|
||||
"p_deactivate_restricted_nfc_footer": null,
|
||||
"s_unsupported_yk": "YubiKey non prise en charge",
|
||||
"s_yk_not_recognized": "Appareil non reconnu",
|
||||
"p_operation_failed_try_again": "L'opération a échoué, veuillez réessayer.",
|
||||
@ -500,6 +504,7 @@
|
||||
}
|
||||
},
|
||||
"@_fingerprints": {},
|
||||
"s_biometrics": null,
|
||||
"l_fingerprint": "Empreinte digitale\u00a0: {label}",
|
||||
"@l_fingerprint": {
|
||||
"placeholders": {
|
||||
@ -660,6 +665,7 @@
|
||||
"s_allow_fingerprint": null,
|
||||
"p_cert_options_desc": "Algorithme clé à utiliser, format de sortie et date d'expiration (certificat uniquement).",
|
||||
"p_cert_options_bio_desc": null,
|
||||
"p_key_options_bio_desc": null,
|
||||
"s_overwrite_slot": "Écraser slot",
|
||||
"p_overwrite_slot_desc": "Cela écrasera définitivement le contenu du slot {slot}.",
|
||||
"@p_overwrite_slot_desc": {
|
||||
|
@ -229,6 +229,10 @@
|
||||
"l_no_yk_present": "YubiKeyがありません",
|
||||
"s_unknown_type": "不明なタイプ",
|
||||
"s_unknown_device": "認識されないデバイス",
|
||||
"s_restricted_nfc": null,
|
||||
"l_deactivate_restricted_nfc": null,
|
||||
"p_deactivate_restricted_nfc_desc": null,
|
||||
"p_deactivate_restricted_nfc_footer": null,
|
||||
"s_unsupported_yk": "サポートされていないYubiKey",
|
||||
"s_yk_not_recognized": "デバイスが認識されません",
|
||||
"p_operation_failed_try_again": "操作に失敗しました。もう一度やり直してください。",
|
||||
@ -500,6 +504,7 @@
|
||||
}
|
||||
},
|
||||
"@_fingerprints": {},
|
||||
"s_biometrics": null,
|
||||
"l_fingerprint": "指紋:{label}",
|
||||
"@l_fingerprint": {
|
||||
"placeholders": {
|
||||
@ -660,6 +665,7 @@
|
||||
"s_allow_fingerprint": null,
|
||||
"p_cert_options_desc": "使用する鍵アルゴリズム、出力形式、および有効期限(証明書のみ)。",
|
||||
"p_cert_options_bio_desc": null,
|
||||
"p_key_options_bio_desc": null,
|
||||
"s_overwrite_slot": "スロットを上書き",
|
||||
"p_overwrite_slot_desc": "これにより、スロット{slot}内の既存コンテンツが完全に上書きされます。",
|
||||
"@p_overwrite_slot_desc": {
|
||||
|
@ -229,6 +229,10 @@
|
||||
"l_no_yk_present": "Nie wykryto YubiKey",
|
||||
"s_unknown_type": "Nieznany typ",
|
||||
"s_unknown_device": "Nierozpoznane urządzenie",
|
||||
"s_restricted_nfc": null,
|
||||
"l_deactivate_restricted_nfc": null,
|
||||
"p_deactivate_restricted_nfc_desc": null,
|
||||
"p_deactivate_restricted_nfc_footer": null,
|
||||
"s_unsupported_yk": "Nieobsługiwany klucz YubiKey",
|
||||
"s_yk_not_recognized": "Urządzenie nie rozpoznane",
|
||||
"p_operation_failed_try_again": null,
|
||||
@ -500,6 +504,7 @@
|
||||
}
|
||||
},
|
||||
"@_fingerprints": {},
|
||||
"s_biometrics": null,
|
||||
"l_fingerprint": "Odcisk palca: {label}",
|
||||
"@l_fingerprint": {
|
||||
"placeholders": {
|
||||
@ -660,6 +665,7 @@
|
||||
"s_allow_fingerprint": null,
|
||||
"p_cert_options_desc": "Algorytm klucza do użycia, format wyjściowy i data wygaśnięcia (tylko certyfikat).",
|
||||
"p_cert_options_bio_desc": null,
|
||||
"p_key_options_bio_desc": null,
|
||||
"s_overwrite_slot": "Nadpisz slot",
|
||||
"p_overwrite_slot_desc": "Spowoduje to trwałe nadpisanie istniejącej zawartości w slocie {slot}.",
|
||||
"@p_overwrite_slot_desc": {
|
||||
|
@ -99,6 +99,7 @@ const appListItem95 = Key('$_prefix.95.applistitem');
|
||||
|
||||
// SlotMetadata body keys
|
||||
const slotMetadataKeyType = Key('$_prefix.slotMetadata.keyType');
|
||||
const slotMetadataBiometrics = Key('$_prefix.slotMetadata.biometrics');
|
||||
|
||||
// CertInfo body keys
|
||||
const certInfoKeyType = Key('$_prefix.certInfo.keyType');
|
||||
|
@ -28,9 +28,10 @@ class CertInfoTable extends ConsumerWidget {
|
||||
final CertInfo? certInfo;
|
||||
final SlotMetadata? metadata;
|
||||
final bool alwaysIncludePrivate;
|
||||
final bool supportsBio;
|
||||
|
||||
const CertInfoTable(this.certInfo, this.metadata,
|
||||
{super.key, this.alwaysIncludePrivate = false});
|
||||
{super.key, this.alwaysIncludePrivate = false, this.supportsBio = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -46,6 +47,16 @@ class CertInfoTable extends ConsumerWidget {
|
||||
metadata.keyType.getDisplayName(l10n),
|
||||
keys.slotMetadataKeyType
|
||||
),
|
||||
if (metadata != null &&
|
||||
metadata.pinPolicy != PinPolicy.never &&
|
||||
supportsBio)
|
||||
l10n.s_biometrics: (
|
||||
[PinPolicy.matchAlways, PinPolicy.matchOnce]
|
||||
.contains(metadata.pinPolicy)
|
||||
? l10n.s_enabled
|
||||
: l10n.s_disabled,
|
||||
keys.slotMetadataBiometrics
|
||||
),
|
||||
if (metadata == null && alwaysIncludePrivate)
|
||||
l10n.s_private_key: (l10n.s_none, keys.slotMetadataKeyType),
|
||||
if (certInfo != null) ...{
|
||||
|
@ -249,11 +249,13 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
|
||||
FilterChip(
|
||||
label: Text(l10n.s_allow_fingerprint),
|
||||
selected: _allowMatch,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
_allowMatch = value;
|
||||
});
|
||||
},
|
||||
onSelected: _generating
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_allowMatch = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
]),
|
||||
Padding(
|
||||
|
@ -292,16 +292,6 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!unsupportedKey && widget.showMatch)
|
||||
FilterChip(
|
||||
label: Text(l10n.s_allow_fingerprint),
|
||||
selected: _allowMatch,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
_allowMatch = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
if (certInfo != null) ...[
|
||||
Text(
|
||||
@ -315,7 +305,25 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
|
||||
140, // Needed for layout, adapt if text sizes changes
|
||||
child: CertInfoTable(certInfo, null),
|
||||
),
|
||||
]
|
||||
],
|
||||
if (keyType != null && !unsupportedKey && widget.showMatch) ...[
|
||||
Text(
|
||||
l10n.s_options,
|
||||
style: textTheme.bodyLarge,
|
||||
),
|
||||
Text(l10n.p_key_options_bio_desc),
|
||||
FilterChip(
|
||||
label: Text(l10n.s_allow_fingerprint),
|
||||
selected: _allowMatch,
|
||||
onSelected: _importing
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_allowMatch = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
|
@ -158,6 +158,7 @@ class _PivScreenState extends ConsumerState<PivScreen> {
|
||||
selected.metadata,
|
||||
alwaysIncludePrivate:
|
||||
pivState.supportsMetadata,
|
||||
supportsBio: pivState.supportsBio,
|
||||
),
|
||||
if (selected.certInfo == null)
|
||||
const SizedBox(height: 16)
|
||||
|
@ -98,6 +98,7 @@ class SlotDialog extends ConsumerWidget {
|
||||
certInfo,
|
||||
metadata,
|
||||
alwaysIncludePrivate: pivState.supportsMetadata,
|
||||
supportsBio: pivState.supportsBio,
|
||||
),
|
||||
if (certInfo == null) const SizedBox(height: 16),
|
||||
],
|
||||
|
@ -27,7 +27,9 @@ List<KeyType> getSupportedKeyTypes(Version version, bool isFips) => [
|
||||
if (!isFips) KeyType.x25519,
|
||||
],
|
||||
KeyType.eccp256,
|
||||
KeyType.eccp384,
|
||||
if (version.isAtLeast(4, 0)) ...[
|
||||
KeyType.eccp384,
|
||||
]
|
||||
];
|
||||
|
||||
PinPolicy getPinPolicy(SlotId slot, bool match) {
|
||||
|
Loading…
Reference in New Issue
Block a user