Merge branch 'main' into adamve/android_fido

This commit is contained in:
Adam Velebil 2024-02-21 13:23:48 +01:00
commit 5f77cfdd8b
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
23 changed files with 264 additions and 158 deletions

View File

@ -1,2 +1,2 @@
FLUTTER=3.16.9 FLUTTER=3.16.9
PYVER=3.12.1 PYVER=3.12.2

View File

@ -21,6 +21,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
echo "PYVER=3.12.1" >> $GITHUB_ENV # Remove once 3.12.2 is available from PPA
export PYVER_MINOR=${PYVER%.*} export PYVER_MINOR=${PYVER%.*}
echo "PYVER_MINOR: $PYVER_MINOR" echo "PYVER_MINOR: $PYVER_MINOR"
apt-get update apt-get update

15
NEWS
View File

@ -1,3 +1,18 @@
* Version 6.4.0 (released 2024-02-20)
** UI: Major UI overhaul, with improvements including:
*** Add new UI layouts for wider windows to better utilize screen space.
*** Add YubiKey personalization through custom naming and theme color.
*** Split FIDO/WebAuthn into multiple sections.
*** Move factory reset functionality into a single dialog, from the individual sections.
** Add support for Yubico OTP provisioning.
** PIV: Display more information about keys and certificates.
** PIV: Add output format for public key when generating keys.
** Desktop: Window hidden/shown state no longer saved when closing the app,
use --hidden to start the app in a hidden to systray state.
** Desktop: Fix FIDO reset over NFC.
** Windows: Add option to launch Windows Settings for FIDO management.
** Android: Increase read timeout for NFC, improving compatibility with older YubiKeys.
* Version 6.3.1 (released 2023-12-12) * Version 6.3.1 (released 2023-12-12)
** Add command line options: --hidden/--shown, --log-file FILE. ** Add command line options: --hidden/--shown, --log-file FILE.
** Disable autocorrect in text fields. ** Disable autocorrect in text fields.

View File

@ -124,10 +124,12 @@ class Ctap2Node(RpcNode):
removed = False removed = False
while not event.wait(0.5): while not event.wait(0.5):
try: try:
with dev.open_connection(FidoConnection): conn = dev.open_connection(FidoConnection)
if removed: if removed:
sleep(1.0) # Wait for the device to settle conn.close()
return dev.open_connection(FidoConnection) sleep(1.0) # Wait for the device to settle
return dev.open_connection(FidoConnection)
conn.close()
except CardConnectionException: except CardConnectionException:
pass # Expected, ignore pass # Expected, ignore
except NoCardException: except NoCardException:

21
helper/poetry.lock generated
View File

@ -235,21 +235,21 @@ files = [
[[package]] [[package]]
name = "jaraco-classes" name = "jaraco-classes"
version = "3.3.0" version = "3.3.1"
description = "Utility functions for Python class constructs" description = "Utility functions for Python class constructs"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, {file = "jaraco.classes-3.3.1-py3-none-any.whl", hash = "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206"},
{file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, {file = "jaraco.classes-3.3.1.tar.gz", hash = "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"},
] ]
[package.dependencies] [package.dependencies]
more-itertools = "*" more-itertools = "*"
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]] [[package]]
name = "jeepney" name = "jeepney"
@ -575,11 +575,16 @@ description = "Smartcard module for Python."
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "pyscard-2.0.7-cp310-cp310-win32.whl", hash = "sha256:06666a597e1293421fa90e0d4fc2418add447b10b7dc85f49b3cafc23480f046"},
{file = "pyscard-2.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a2266345bd387854298153264bff8b74f494581880a76e3e8679460c1b090fab"}, {file = "pyscard-2.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a2266345bd387854298153264bff8b74f494581880a76e3e8679460c1b090fab"},
{file = "pyscard-2.0.7-cp311-cp311-win32.whl", hash = "sha256:beacdcdc3d1516e195f7a38ec3966c5d4df7390c8f036cb41f6fef72bc5cc646"},
{file = "pyscard-2.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e37b697327e8dc4848c481428d1cbd10b7ae2ce037bc799e5b8bbd2fc3ab5ed"}, {file = "pyscard-2.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e37b697327e8dc4848c481428d1cbd10b7ae2ce037bc799e5b8bbd2fc3ab5ed"},
{file = "pyscard-2.0.7-cp37-cp37m-win32.whl", hash = "sha256:a0c5edbedafba62c68160884f878d9f53996d7219a3fc11b1cea6bab59c7f34a"},
{file = "pyscard-2.0.7-cp37-cp37m-win_amd64.whl", hash = "sha256:f704ad40dc40306e1c0981941789518ab16aa1f84443b1d52ec0264884092b3b"}, {file = "pyscard-2.0.7-cp37-cp37m-win_amd64.whl", hash = "sha256:f704ad40dc40306e1c0981941789518ab16aa1f84443b1d52ec0264884092b3b"},
{file = "pyscard-2.0.7-cp38-cp38-win32.whl", hash = "sha256:59a466ab7ae20188dd197664b9ca1ea9524d115a5aa5b16b575a6b772cdcb73c"},
{file = "pyscard-2.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:da70aa5b7be5868b88cdb6d4a419d2791b6165beeb90cd01d2748033302a0f43"}, {file = "pyscard-2.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:da70aa5b7be5868b88cdb6d4a419d2791b6165beeb90cd01d2748033302a0f43"},
{file = "pyscard-2.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2d4bdc1f4e0e6c46e417ac1bc9d5990f7cfb24a080e890d453781405f7bd29dc"}, {file = "pyscard-2.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2d4bdc1f4e0e6c46e417ac1bc9d5990f7cfb24a080e890d453781405f7bd29dc"},
{file = "pyscard-2.0.7-cp39-cp39-win32.whl", hash = "sha256:39e030c47878b37ae08038a917959357be6468da52e8b144e84ffc659f50e6e2"},
{file = "pyscard-2.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:5a5865675be294c8d91f22dc91e7d897c4138881e5295fb6b2cd821f7c0389d9"}, {file = "pyscard-2.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:5a5865675be294c8d91f22dc91e7d897c4138881e5295fb6b2cd821f7c0389d9"},
{file = "pyscard-2.0.7.tar.gz", hash = "sha256:278054525fa75fbe8b10460d87edcd03a70ad94d688b11345e4739987f85c1bf"}, {file = "pyscard-2.0.7.tar.gz", hash = "sha256:278054525fa75fbe8b10460d87edcd03a70ad94d688b11345e4739987f85c1bf"},
] ]
@ -688,13 +693,13 @@ files = [
[[package]] [[package]]
name = "types-pillow" name = "types-pillow"
version = "10.2.0.20240125" version = "10.2.0.20240206"
description = "Typing stubs for Pillow" description = "Typing stubs for Pillow"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-Pillow-10.2.0.20240125.tar.gz", hash = "sha256:c449b2c43b9fdbe0494a7b950e6b39a4e50516091213fec24ef3f33c1d017717"}, {file = "types-Pillow-10.2.0.20240206.tar.gz", hash = "sha256:f0de5107ff8362ffdbbd53ec896202ac905e6ab22ae784b46bcdad160ea143b9"},
{file = "types_Pillow-10.2.0.20240125-py3-none-any.whl", hash = "sha256:322dbae32b4b7918da5e8a47c50ac0f24b0aa72a804a23857620f2722b03c858"}, {file = "types_Pillow-10.2.0.20240206-py3-none-any.whl", hash = "sha256:abc339ae28af5916146a7729261480d68ac902cd4ff57e0bdd402eee7962644d"},
] ]
[[package]] [[package]]

View File

@ -6,8 +6,8 @@ VSVersionInfo(
ffi=FixedFileInfo( ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0. # Set not needed items to zero 0.
filevers=(6, 4, 0, 0), filevers=(6, 4, 1, 0),
prodvers=(6, 4, 0, 0), prodvers=(6, 4, 1, 0),
# Contains a bitmask that specifies the valid bits 'flags'r # Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f, mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file. # Contains a bitmask that specifies the Boolean attributes of the file.
@ -31,11 +31,11 @@ VSVersionInfo(
'040904b0', '040904b0',
[StringStruct('CompanyName', 'Yubico'), [StringStruct('CompanyName', 'Yubico'),
StringStruct('FileDescription', 'Yubico Authenticator Helper'), StringStruct('FileDescription', 'Yubico Authenticator Helper'),
StringStruct('FileVersion', '6.4.0-dev.0'), StringStruct('FileVersion', '6.4.1-dev.0'),
StringStruct('LegalCopyright', 'Copyright (c) Yubico'), StringStruct('LegalCopyright', 'Copyright (c) Yubico'),
StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('OriginalFilename', 'authenticator-helper.exe'),
StringStruct('ProductName', 'Yubico Authenticator'), StringStruct('ProductName', 'Yubico Authenticator'),
StringStruct('ProductVersion', '6.4.0-dev.0')]) StringStruct('ProductVersion', '6.4.1-dev.0')])
]), ]),
VarFileInfo([VarStruct('Translation', [1033, 1200])]) VarFileInfo([VarStruct('Translation', [1033, 1200])])
] ]

View File

@ -43,7 +43,7 @@ class ElevateFidoButtons extends ConsumerWidget {
duration: const Duration(seconds: 30)); duration: const Duration(seconds: 30));
try { try {
if (await ref.read(rpcProvider).requireValue.elevate()) { if (await ref.read(rpcProvider).requireValue.elevate()) {
ref.invalidate(rpcProvider); ref.invalidate(rpcStateProvider);
} else { } else {
await ref.read(withContextProvider)((context) async => await ref.read(withContextProvider)((context) async =>
showMessage(context, l10n.s_permission_denied)); showMessage(context, l10n.s_permission_denied));

View File

@ -15,6 +15,7 @@
*/ */
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -25,6 +26,7 @@ import '../../app/logging.dart';
import '../../core/models.dart'; import '../../core/models.dart';
import '../../core/state.dart'; import '../../core/state.dart';
import '../../desktop/models.dart'; import '../../desktop/models.dart';
import '../../desktop/state.dart';
import '../../fido/models.dart'; import '../../fido/models.dart';
import '../../fido/state.dart'; import '../../fido/state.dart';
import '../../management/models.dart'; import '../../management/models.dart';
@ -36,6 +38,7 @@ import '../features.dart' as features;
import '../message.dart'; import '../message.dart';
import '../models.dart'; import '../models.dart';
import '../state.dart'; import '../state.dart';
import 'elevate_fido_buttons.dart';
import 'keys.dart'; import 'keys.dart';
final _log = Logger('fido.views.reset_dialog'); final _log = Logger('fido.views.reset_dialog');
@ -68,12 +71,19 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
StreamSubscription<InteractionEvent>? _subscription; StreamSubscription<InteractionEvent>? _subscription;
InteractionEvent? _interaction; InteractionEvent? _interaction;
int _currentStep = -1; int _currentStep = -1;
final _totalSteps = 3; late final int _totalSteps;
@override
void initState() {
super.initState();
final nfc = widget.data.node.transport == Transport.nfc;
_totalSteps = nfc ? 2 : 3;
}
String _getMessage() { String _getMessage() {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final nfc = widget.data.node.transport == Transport.nfc; final nfc = widget.data.node.transport == Transport.nfc;
if (_currentStep == 3) { if (_currentStep == _totalSteps) {
return l10n.l_fido_app_reset; return l10n.l_fido_app_reset;
} }
return switch (_interaction) { return switch (_interaction) {
@ -99,20 +109,26 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
.contains(widget.data.info.formFactor); .contains(widget.data.info.formFactor);
final globalReset = isBio && (supported & Capability.piv.value) != 0; final globalReset = isBio && (supported & Capability.piv.value) != 0;
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
double progress = _currentStep == -1 ? 0.0 : _currentStep / (_totalSteps); double progress = _currentStep == -1 ? 0.0 : _currentStep / (_totalSteps);
final needsElevation = Platform.isWindows &&
_application == Capability.fido2 &&
!ref.watch(rpcStateProvider.select((state) => state.isAdmin));
return ResponsiveDialog( return ResponsiveDialog(
title: Text(l10n.s_factory_reset), title: Text(l10n.s_factory_reset),
key: factoryResetCancel, key: factoryResetCancel,
onCancel: switch (_application) { onCancel: switch (_application) {
Capability.fido2 => _currentStep < 3 Capability.fido2 => _currentStep < _totalSteps
? () { ? () {
_currentStep = -1;
_subscription?.cancel(); _subscription?.cancel();
} }
: null, : null,
_ => null, _ => null,
}, },
actions: [ actions: [
if (_currentStep < 3) if (_currentStep < _totalSteps)
TextButton( TextButton(
onPressed: switch (_application) { onPressed: switch (_application) {
Capability.fido2 => _subscription == null Capability.fido2 => _subscription == null
@ -222,7 +238,8 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
: null, : null,
tooltip: tooltip:
!showLabels ? c.getDisplayName(l10n) : null, !showLabels ? c.getDisplayName(l10n) : null,
enabled: enabled & c.value != 0, enabled:
enabled & c.value != 0 && (_currentStep == -1),
)) ))
.toList(), .toList(),
selected: _application != null ? {_application!} : {}, selected: _application != null ? {_application!} : {},
@ -247,16 +264,21 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
.bodyMedium .bodyMedium
?.copyWith(fontWeight: FontWeight.w700), ?.copyWith(fontWeight: FontWeight.w700),
), ),
Text( if (needsElevation) ...[
switch (_application) { Text(l10n.p_elevated_permissions_required),
Capability.oath => l10n.p_warning_disable_credentials, const ElevateFidoButtons(),
Capability.piv => l10n.p_warning_piv_reset_desc, ] else ...[
Capability.fido2 => l10n.p_warning_disable_accounts, Text(
_ => globalReset switch (_application) {
? l10n.p_warning_global_reset_desc Capability.oath => l10n.p_warning_disable_credentials,
: l10n.p_factory_reset_desc, Capability.piv => l10n.p_warning_piv_reset_desc,
}, Capability.fido2 => l10n.p_warning_disable_accounts,
), _ => globalReset
? l10n.p_warning_global_reset_desc
: l10n.p_factory_reset_desc,
},
),
],
if (_application == Capability.fido2 && _currentStep >= 0) ...[ if (_application == Capability.fido2 && _currentStep >= 0) ...[
Text('${l10n.s_status}: ${_getMessage()}'), Text('${l10n.s_status}: ${_getMessage()}'),
LinearProgressIndicator(value: progress) LinearProgressIndicator(value: progress)

View File

@ -479,6 +479,7 @@
"l_import_nothing": null, "l_import_nothing": null,
"l_importing_file": null, "l_importing_file": null,
"s_file_imported": null, "s_file_imported": null,
"l_unsupported_key_type": null,
"l_delete_certificate": null, "l_delete_certificate": null,
"l_delete_certificate_desc": null, "l_delete_certificate_desc": null,
"s_issuer": null, "s_issuer": null,

View File

@ -479,6 +479,7 @@
"l_import_nothing": "Nothing to import", "l_import_nothing": "Nothing to import",
"l_importing_file": "Importing file\u2026", "l_importing_file": "Importing file\u2026",
"s_file_imported": "File imported", "s_file_imported": "File imported",
"l_unsupported_key_type": "Unsupported key type",
"l_delete_certificate": "Delete certificate", "l_delete_certificate": "Delete certificate",
"l_delete_certificate_desc": "Remove the certificate from your YubiKey", "l_delete_certificate_desc": "Remove the certificate from your YubiKey",
"s_issuer": "Issuer", "s_issuer": "Issuer",

View File

@ -479,6 +479,7 @@
"l_import_nothing": null, "l_import_nothing": null,
"l_importing_file": "Importation d'un fichier\u2026", "l_importing_file": "Importation d'un fichier\u2026",
"s_file_imported": "Fichier importé", "s_file_imported": "Fichier importé",
"l_unsupported_key_type": null,
"l_delete_certificate": "Supprimer un certificat", "l_delete_certificate": "Supprimer un certificat",
"l_delete_certificate_desc": "Supprimer un certificat de votre YubiKey", "l_delete_certificate_desc": "Supprimer un certificat de votre YubiKey",
"s_issuer": "Émetteur", "s_issuer": "Émetteur",

View File

@ -479,6 +479,7 @@
"l_import_nothing": null, "l_import_nothing": null,
"l_importing_file": "ファイルのインポート中\u2026", "l_importing_file": "ファイルのインポート中\u2026",
"s_file_imported": "ファイル をインポートしました", "s_file_imported": "ファイル をインポートしました",
"l_unsupported_key_type": null,
"l_delete_certificate": "証明書を削除", "l_delete_certificate": "証明書を削除",
"l_delete_certificate_desc": "YubiKeyか証明書の削除", "l_delete_certificate_desc": "YubiKeyか証明書の削除",
"s_issuer": "発行者", "s_issuer": "発行者",

View File

@ -479,6 +479,7 @@
"l_import_nothing": null, "l_import_nothing": null,
"l_importing_file": "Importowanie pliku\u2026", "l_importing_file": "Importowanie pliku\u2026",
"s_file_imported": "Plik został zaimportowany", "s_file_imported": "Plik został zaimportowany",
"l_unsupported_key_type": null,
"l_delete_certificate": "Usuń certyfikat", "l_delete_certificate": "Usuń certyfikat",
"l_delete_certificate_desc": "Usuń certyfikat z klucza YubiKey", "l_delete_certificate_desc": "Usuń certyfikat z klucza YubiKey",
"s_issuer": "Wydawca", "s_issuer": "Wydawca",

View File

@ -83,7 +83,7 @@ class _ConfigureStaticDialogState extends ConsumerState<ConfigureStaticDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final password = _passwordController.text.replaceAll(' ', ''); final password = _passwordController.text;
final passwordLengthValid = final passwordLengthValid =
password.isNotEmpty && password.length <= passwordMaxLength; password.isNotEmpty && password.length <= passwordMaxLength;
final passwordFormatValid = final passwordFormatValid =

View File

@ -77,7 +77,11 @@ enum PinPolicy {
@JsonValue(0x02) @JsonValue(0x02)
once, once,
@JsonValue(0x03) @JsonValue(0x03)
always; always,
@JsonValue(0x04)
matchOnce,
@JsonValue(0x05)
matchAlways;
const PinPolicy(); const PinPolicy();

View File

@ -84,6 +84,8 @@ const _$PinPolicyEnumMap = {
PinPolicy.never: 1, PinPolicy.never: 1,
PinPolicy.once: 2, PinPolicy.once: 2,
PinPolicy.always: 3, PinPolicy.always: 3,
PinPolicy.matchOnce: 4,
PinPolicy.matchAlways: 5,
}; };
_$PivStateMetadataImpl _$$PivStateMetadataImplFromJson( _$PivStateMetadataImpl _$$PivStateMetadataImplFromJson(

View File

@ -30,6 +30,7 @@ import '../keys.dart' as keys;
import '../models.dart'; import '../models.dart';
import '../state.dart'; import '../state.dart';
import 'overwrite_confirm_dialog.dart'; import 'overwrite_confirm_dialog.dart';
import 'utils.dart';
class GenerateKeyDialog extends ConsumerStatefulWidget { class GenerateKeyDialog extends ConsumerStatefulWidget {
final DevicePath devicePath; final DevicePath devicePath;
@ -65,19 +66,6 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
_validToMax = DateTime.utc(now.year + 10, now.month, now.day); _validToMax = DateTime.utc(now.year + 10, now.month, now.day);
} }
List<KeyType> _getSupportedKeyTypes(bool isFips) => [
if (!isFips) KeyType.rsa1024,
KeyType.rsa2048,
if (widget.pivState.version.isAtLeast(5, 7)) ...[
KeyType.rsa3072,
KeyType.rsa4096,
KeyType.ed25519,
if (!isFips) KeyType.x25519,
],
KeyType.eccp256,
KeyType.eccp384,
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
@ -202,7 +190,8 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
runSpacing: 8.0, runSpacing: 8.0,
children: [ children: [
ChoiceFilterChip<KeyType>( ChoiceFilterChip<KeyType>(
items: _getSupportedKeyTypes(isFips), items:
getSupportedKeyTypes(widget.pivState.version, isFips),
value: _keyType, value: _keyType,
selected: _keyType != defaultKeyType, selected: _keyType != defaultKeyType,
itemBuilder: (value) => Text(value.getDisplayName(l10n)), itemBuilder: (value) => Text(value.getDisplayName(l10n)),

View File

@ -31,6 +31,7 @@ import '../models.dart';
import '../state.dart'; import '../state.dart';
import 'cert_info_view.dart'; import 'cert_info_view.dart';
import 'overwrite_confirm_dialog.dart'; import 'overwrite_confirm_dialog.dart';
import 'utils.dart';
class ImportFileDialog extends ConsumerStatefulWidget { class ImportFileDialog extends ConsumerStatefulWidget {
final DevicePath devicePath; final DevicePath devicePath;
@ -86,10 +87,13 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme;
// This is what ListTile uses for subtitle // This is what ListTile uses for subtitle
final subtitleStyle = textTheme.bodyMedium!.copyWith( final subtitleStyle = textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: colorScheme.onSurfaceVariant,
); );
// This is what TextInput errors look like
final errorStyle = textTheme.labelLarge!.copyWith(color: colorScheme.error);
final state = _state; final state = _state;
if (state == null) { if (state == null) {
return ResponsiveDialog( return ResponsiveDialog(
@ -166,116 +170,142 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
), ),
), ),
), ),
result: (_, keyType, certInfo) => ResponsiveDialog( result: (_, keyType, certInfo) {
title: Text(l10n.l_import_file), final isFips =
actions: [ ref.watch(currentDeviceDataProvider).valueOrNull?.info.isFips ??
TextButton( false;
key: keys.unlockButton, final unsupportedKey = keyType != null &&
onPressed: (keyType == null && certInfo == null) || _importing !getSupportedKeyTypes(widget.pivState.version, isFips)
? null .contains(keyType);
: () async { return ResponsiveDialog(
final withContext = ref.read(withContextProvider); title: Text(l10n.l_import_file),
actions: [
TextButton(
key: keys.unlockButton,
onPressed: (keyType == null && certInfo == null) ||
_importing ||
unsupportedKey
? null
: () async {
final withContext = ref.read(withContextProvider);
if (!await confirmOverwrite( if (!await confirmOverwrite(
context, context,
widget.pivSlot, widget.pivSlot,
writeKey: keyType != null, writeKey: keyType != null,
writeCert: certInfo != null, writeCert: certInfo != null,
)) { )) {
return; return;
} }
setState(() {
_importing = true;
});
void Function()? close;
try {
close = await withContext<void Function()>(
(context) async => showMessage(
context,
l10n.l_importing_file,
duration: const Duration(seconds: 30),
));
await ref
.read(pivSlotsProvider(widget.devicePath).notifier)
.import(widget.pivSlot.slot, _data,
password:
_password.isNotEmpty ? _password : null);
await withContext(
(context) async {
Navigator.of(context).pop(true);
showMessage(context, l10n.s_file_imported);
},
);
} catch (err) {
// TODO: More error cases
setState(() { setState(() {
_passwordIsWrong = true; _importing = true;
_importing = false;
}); });
} finally {
close?.call(); void Function()? close;
} try {
}, close = await withContext<void Function()>(
child: Text(l10n.s_import), (context) async => showMessage(
), context,
], l10n.l_importing_file,
child: Padding( duration: const Duration(seconds: 30),
padding: const EdgeInsets.symmetric(horizontal: 18.0), ));
child: Column( await ref
crossAxisAlignment: CrossAxisAlignment.start, .read(pivSlotsProvider(widget.devicePath).notifier)
children: [ .import(widget.pivSlot.slot, _data,
Text(l10n.p_import_items_desc( password:
widget.pivSlot.slot.getDisplayName(l10n))), _password.isNotEmpty ? _password : null);
if (keyType == null && certInfo == null) ...[ await withContext(
Text( (context) async {
l10n.l_import_nothing, Navigator.of(context).pop(true);
style: subtitleStyle, showMessage(context, l10n.s_file_imported);
softWrap: true, },
textAlign: TextAlign.center, );
), } catch (err) {
], // TODO: More error cases
if (keyType != null) ...[ setState(() {
Text( _passwordIsWrong = true;
l10n.s_private_key, _importing = false;
style: textTheme.bodyLarge, });
softWrap: true, } finally {
textAlign: TextAlign.center, close?.call();
), }
Row( },
mainAxisSize: MainAxisSize.min, child: Text(l10n.s_import),
children: [ ),
Text(l10n.s_algorithm), ],
const SizedBox(width: 8), child: Padding(
Text( padding: const EdgeInsets.symmetric(horizontal: 18.0),
keyType.name.toUpperCase(), child: Column(
style: subtitleStyle, crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.p_import_items_desc(
widget.pivSlot.slot.getDisplayName(l10n))),
if (keyType == null && certInfo == null) ...[
Row(
children: [
Icon(Icons.error, color: colorScheme.error),
const SizedBox(width: 8),
Text(
l10n.l_import_nothing,
style: errorStyle,
),
],
),
],
if (keyType != null) ...[
Text(
l10n.s_private_key,
style: textTheme.bodyLarge,
softWrap: true,
textAlign: TextAlign.center,
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(l10n.s_algorithm),
const SizedBox(width: 8),
Text(
keyType.name.toUpperCase(),
style: subtitleStyle,
),
],
),
if (unsupportedKey)
Row(
children: [
Icon(Icons.error, color: colorScheme.error),
const SizedBox(width: 8),
Text(
l10n.l_unsupported_key_type,
style: errorStyle,
),
],
), ),
], ],
) if (certInfo != null) ...[
], Text(
if (certInfo != null) ...[ l10n.s_certificate,
Text( style: textTheme.bodyLarge,
l10n.s_certificate, softWrap: true,
style: textTheme.bodyLarge, textAlign: TextAlign.center,
softWrap: true, ),
textAlign: TextAlign.center, SizedBox(
), height:
SizedBox( 140, // Needed for layout, adapt if text sizes changes
height: 140, // Needed for layout, adapt if text sizes changes child: CertInfoTable(certInfo, null),
child: CertInfoTable(certInfo, null), ),
), ]
] ]
] .map((e) => Padding(
.map((e) => Padding( padding: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.symmetric(vertical: 8.0), child: e,
child: e, ))
)) .toList(),
.toList(), ),
), ),
), );
), },
); );
} }
} }

31
lib/piv/views/utils.dart Normal file
View File

@ -0,0 +1,31 @@
/*
* 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.
*/
import '../../core/models.dart';
import '../models.dart';
List<KeyType> getSupportedKeyTypes(Version version, bool isFips) => [
if (!isFips) KeyType.rsa1024,
KeyType.rsa2048,
if (version.isAtLeast(5, 7)) ...[
KeyType.rsa3072,
KeyType.rsa4096,
KeyType.ed25519,
if (!isFips) KeyType.x25519,
],
KeyType.eccp256,
KeyType.eccp384,
];

View File

@ -1,5 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// This file is generated by running ./set-version.py <version> <build> // This file is generated by running ./set-version.py <version> <build>
const String version = '6.4.0-dev.0'; const String version = '6.4.1-dev.0';
const int build = 60400; const int build = 60402;

View File

@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# This field is updated by running ./set-version.py <version> # This field is updated by running ./set-version.py <version>
# DO NOT MANUALLY EDIT THIS! # DO NOT MANUALLY EDIT THIS!
version: 6.4.0-dev.0+60400 version: 6.4.1-dev.0+60402
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'

View File

@ -1,4 +1,4 @@
$version="6.4.0-dev.0" $version="6.4.1-dev.0"
echo "Clean-up of old files" echo "Clean-up of old files"
rm *.msi rm *.msi

View File

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?define ProductVersion="6.4.0" ?> <?define ProductVersion="6.4.1" ?>
<?define ProductName="Yubico Authenticator" ?> <?define ProductName="Yubico Authenticator" ?>
<Product Id="*" UpgradeCode="fcbafc57-aaaa-47b8-b861-20bda48cd4f6" Name="$(var.ProductName)" Version="$(var.ProductVersion)" Manufacturer="Yubico AB" Language="1033"> <Product Id="*" UpgradeCode="fcbafc57-aaaa-47b8-b861-20bda48cd4f6" Name="$(var.ProductName)" Version="$(var.ProductVersion)" Manufacturer="Yubico AB" Language="1033">