From fb2857f7bc83ee9db4f96ff4c5f8ded47eb7f3b7 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 9 Feb 2024 11:39:35 +0100 Subject: [PATCH 01/11] Bump Python and Python deps --- .github/workflows/env | 2 +- helper/poetry.lock | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/env b/.github/workflows/env index 7721be85..7ba02eb3 100644 --- a/.github/workflows/env +++ b/.github/workflows/env @@ -1,2 +1,2 @@ FLUTTER=3.16.9 -PYVER=3.12.1 +PYVER=3.12.2 diff --git a/helper/poetry.lock b/helper/poetry.lock index 4aca1246..2fd6c000 100644 --- a/helper/poetry.lock +++ b/helper/poetry.lock @@ -235,21 +235,21 @@ files = [ [[package]] name = "jaraco-classes" -version = "3.3.0" +version = "3.3.1" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, - {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, + {file = "jaraco.classes-3.3.1-py3-none-any.whl", hash = "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206"}, + {file = "jaraco.classes-3.3.1.tar.gz", hash = "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"}, ] [package.dependencies] more-itertools = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "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"] +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-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jeepney" @@ -575,11 +575,16 @@ description = "Smartcard module for Python." optional = false python-versions = "*" 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-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-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-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-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.tar.gz", hash = "sha256:278054525fa75fbe8b10460d87edcd03a70ad94d688b11345e4739987f85c1bf"}, ] @@ -688,13 +693,13 @@ files = [ [[package]] name = "types-pillow" -version = "10.2.0.20240125" +version = "10.2.0.20240206" description = "Typing stubs for Pillow" optional = false python-versions = ">=3.8" files = [ - {file = "types-Pillow-10.2.0.20240125.tar.gz", hash = "sha256:c449b2c43b9fdbe0494a7b950e6b39a4e50516091213fec24ef3f33c1d017717"}, - {file = "types_Pillow-10.2.0.20240125-py3-none-any.whl", hash = "sha256:322dbae32b4b7918da5e8a47c50ac0f24b0aa72a804a23857620f2722b03c858"}, + {file = "types-Pillow-10.2.0.20240206.tar.gz", hash = "sha256:f0de5107ff8362ffdbbd53ec896202ac905e6ab22ae784b46bcdad160ea143b9"}, + {file = "types_Pillow-10.2.0.20240206-py3-none-any.whl", hash = "sha256:abc339ae28af5916146a7729261480d68ac902cd4ff57e0bdd402eee7962644d"}, ] [[package]] From 9c258817660da17bc95ebae66336d53dd6b981ef Mon Sep 17 00:00:00 2001 From: Elias Bonnici Date: Mon, 12 Feb 2024 09:45:13 +0100 Subject: [PATCH 02/11] Bump version number. --- helper/version_info.txt | 4 ++-- lib/version.dart | 2 +- pubspec.yaml | 2 +- resources/win/release-win.ps1 | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index dd2a3251..2c22c050 100755 --- a/helper/version_info.txt +++ b/helper/version_info.txt @@ -31,11 +31,11 @@ VSVersionInfo( '040904b0', [StringStruct('CompanyName', 'Yubico'), StringStruct('FileDescription', 'Yubico Authenticator Helper'), - StringStruct('FileVersion', '6.4.0-dev.0'), + StringStruct('FileVersion', '6.4.0'), StringStruct('LegalCopyright', 'Copyright (c) Yubico'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '6.4.0-dev.0')]) + StringStruct('ProductVersion', '6.4.0')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index bf6a30e8..400c699c 100755 --- a/lib/version.dart +++ b/lib/version.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // This file is generated by running ./set-version.py -const String version = '6.4.0-dev.0'; +const String version = '6.4.0'; const int build = 60400; diff --git a/pubspec.yaml b/pubspec.yaml index e2260d7d..1f285a6a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # DO NOT MANUALLY EDIT THIS! -version: 6.4.0-dev.0+60400 +version: 6.4.0+60400 environment: sdk: '>=3.0.0 <4.0.0' diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index fa990184..bf35ddf0 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,4 +1,4 @@ -$version="6.4.0-dev.0" +$version="6.4.0" echo "Clean-up of old files" rm *.msi From f03054886ebf05fab58b19e3988f44f452595918 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 12 Feb 2024 13:34:19 +0100 Subject: [PATCH 03/11] Don't remove spaces from static password --- lib/otp/views/configure_static_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/otp/views/configure_static_dialog.dart b/lib/otp/views/configure_static_dialog.dart index 729ff30f..1f9faf39 100644 --- a/lib/otp/views/configure_static_dialog.dart +++ b/lib/otp/views/configure_static_dialog.dart @@ -83,7 +83,7 @@ class _ConfigureStaticDialogState extends ConsumerState { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; - final password = _passwordController.text.replaceAll(' ', ''); + final password = _passwordController.text; final passwordLengthValid = password.isNotEmpty && password.length <= passwordMaxLength; final passwordFormatValid = From 5f476b612db03f6e0b8c7b1267553528a80fa97b Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 12 Feb 2024 14:33:08 +0100 Subject: [PATCH 04/11] PIV: Prevent import of unsupported keys --- lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/l10n/app_ja.arb | 1 + lib/l10n/app_pl.arb | 1 + lib/piv/views/generate_key_dialog.dart | 17 +- lib/piv/views/import_file_dialog.dart | 238 ++++++++++++++----------- lib/piv/views/utils.dart | 31 ++++ 8 files changed, 173 insertions(+), 118 deletions(-) create mode 100644 lib/piv/views/utils.dart diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index f190078e..192e116a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -479,6 +479,7 @@ "l_import_nothing": null, "l_importing_file": null, "s_file_imported": null, + "l_unsupported_key_type": null, "l_delete_certificate": null, "l_delete_certificate_desc": null, "s_issuer": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 137bf4e7..cbb83c9d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -479,6 +479,7 @@ "l_import_nothing": "Nothing to import", "l_importing_file": "Importing file\u2026", "s_file_imported": "File imported", + "l_unsupported_key_type": "Unsupported key type", "l_delete_certificate": "Delete certificate", "l_delete_certificate_desc": "Remove the certificate from your YubiKey", "s_issuer": "Issuer", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 5867c718..0c96c34a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -479,6 +479,7 @@ "l_import_nothing": null, "l_importing_file": "Importation d'un fichier\u2026", "s_file_imported": "Fichier importé", + "l_unsupported_key_type": null, "l_delete_certificate": "Supprimer un certificat", "l_delete_certificate_desc": "Supprimer un certificat de votre YubiKey", "s_issuer": "Émetteur", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index d61b7553..e6dca26f 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -479,6 +479,7 @@ "l_import_nothing": null, "l_importing_file": "ファイルのインポート中\u2026", "s_file_imported": "ファイル をインポートしました", + "l_unsupported_key_type": null, "l_delete_certificate": "証明書を削除", "l_delete_certificate_desc": "YubiKeyか証明書の削除", "s_issuer": "発行者", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 1138926d..3ceb920f 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -479,6 +479,7 @@ "l_import_nothing": null, "l_importing_file": "Importowanie pliku\u2026", "s_file_imported": "Plik został zaimportowany", + "l_unsupported_key_type": null, "l_delete_certificate": "Usuń certyfikat", "l_delete_certificate_desc": "Usuń certyfikat z klucza YubiKey", "s_issuer": "Wydawca", diff --git a/lib/piv/views/generate_key_dialog.dart b/lib/piv/views/generate_key_dialog.dart index 49b6cd17..f4451452 100644 --- a/lib/piv/views/generate_key_dialog.dart +++ b/lib/piv/views/generate_key_dialog.dart @@ -30,6 +30,7 @@ import '../keys.dart' as keys; import '../models.dart'; import '../state.dart'; import 'overwrite_confirm_dialog.dart'; +import 'utils.dart'; class GenerateKeyDialog extends ConsumerStatefulWidget { final DevicePath devicePath; @@ -65,19 +66,6 @@ class _GenerateKeyDialogState extends ConsumerState { _validToMax = DateTime.utc(now.year + 10, now.month, now.day); } - List _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 Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; @@ -202,7 +190,8 @@ class _GenerateKeyDialogState extends ConsumerState { runSpacing: 8.0, children: [ ChoiceFilterChip( - items: _getSupportedKeyTypes(isFips), + items: + getSupportedKeyTypes(widget.pivState.version, isFips), value: _keyType, selected: _keyType != defaultKeyType, itemBuilder: (value) => Text(value.getDisplayName(l10n)), diff --git a/lib/piv/views/import_file_dialog.dart b/lib/piv/views/import_file_dialog.dart index bd5a14e2..7f19c94f 100644 --- a/lib/piv/views/import_file_dialog.dart +++ b/lib/piv/views/import_file_dialog.dart @@ -31,6 +31,7 @@ import '../models.dart'; import '../state.dart'; import 'cert_info_view.dart'; import 'overwrite_confirm_dialog.dart'; +import 'utils.dart'; class ImportFileDialog extends ConsumerStatefulWidget { final DevicePath devicePath; @@ -86,10 +87,13 @@ class _ImportFileDialogState extends ConsumerState { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final textTheme = Theme.of(context).textTheme; + final colorScheme = Theme.of(context).colorScheme; // This is what ListTile uses for subtitle 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; if (state == null) { return ResponsiveDialog( @@ -166,116 +170,142 @@ class _ImportFileDialogState extends ConsumerState { ), ), ), - result: (_, keyType, certInfo) => ResponsiveDialog( - title: Text(l10n.l_import_file), - actions: [ - TextButton( - key: keys.unlockButton, - onPressed: (keyType == null && certInfo == null) || _importing - ? null - : () async { - final withContext = ref.read(withContextProvider); + result: (_, keyType, certInfo) { + final isFips = + ref.watch(currentDeviceDataProvider).valueOrNull?.info.isFips ?? + false; + final unsupportedKey = keyType != null && + !getSupportedKeyTypes(widget.pivState.version, isFips) + .contains(keyType); + return ResponsiveDialog( + 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( - context, - widget.pivSlot, - writeKey: keyType != null, - writeCert: certInfo != null, - )) { - return; - } + if (!await confirmOverwrite( + context, + widget.pivSlot, + writeKey: keyType != null, + writeCert: certInfo != null, + )) { + return; + } - setState(() { - _importing = true; - }); - - void Function()? close; - try { - close = await withContext( - (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(() { - _passwordIsWrong = true; - _importing = false; + _importing = true; }); - } finally { - close?.call(); - } - }, - child: Text(l10n.s_import), - ), - ], - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(l10n.p_import_items_desc( - widget.pivSlot.slot.getDisplayName(l10n))), - if (keyType == null && certInfo == null) ...[ - Text( - l10n.l_import_nothing, - style: subtitleStyle, - softWrap: true, - textAlign: TextAlign.center, - ), - ], - 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, + + void Function()? close; + try { + close = await withContext( + (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(() { + _passwordIsWrong = true; + _importing = false; + }); + } finally { + close?.call(); + } + }, + child: Text(l10n.s_import), + ), + ], + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0), + child: Column( + 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( - l10n.s_certificate, - style: textTheme.bodyLarge, - softWrap: true, - textAlign: TextAlign.center, - ), - SizedBox( - height: 140, // Needed for layout, adapt if text sizes changes - child: CertInfoTable(certInfo, null), - ), + ], + if (certInfo != null) ...[ + Text( + l10n.s_certificate, + style: textTheme.bodyLarge, + softWrap: true, + textAlign: TextAlign.center, + ), + SizedBox( + height: + 140, // Needed for layout, adapt if text sizes changes + child: CertInfoTable(certInfo, null), + ), + ] ] - ] - .map((e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: e, - )) - .toList(), + .map((e) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: e, + )) + .toList(), + ), ), - ), - ), + ); + }, ); } } diff --git a/lib/piv/views/utils.dart b/lib/piv/views/utils.dart new file mode 100644 index 00000000..c0357d2a --- /dev/null +++ b/lib/piv/views/utils.dart @@ -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 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, + ]; From dfeb3e0a558e44438a2bd1e26a5837912f5c9881 Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Mon, 12 Feb 2024 15:40:01 +0100 Subject: [PATCH 05/11] FIDO2 reset: make sure you cannot change to another application while resetting FIDO2 --- lib/app/views/reset_dialog.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index e7ce016d..e319844c 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -69,6 +69,7 @@ class _ResetDialogState extends ConsumerState { InteractionEvent? _interaction; int _currentStep = -1; final _totalSteps = 3; + bool _resetInProgress = false; String _getMessage() { final l10n = AppLocalizations.of(context)!; @@ -117,6 +118,7 @@ class _ResetDialogState extends ConsumerState { onPressed: switch (_application) { Capability.fido2 => _subscription == null ? () async { + _resetInProgress = true; _subscription = ref .read( fidoStateProvider(widget.data.node.path).notifier) @@ -222,7 +224,8 @@ class _ResetDialogState extends ConsumerState { : null, tooltip: !showLabels ? c.getDisplayName(l10n) : null, - enabled: enabled & c.value != 0, + enabled: + enabled & c.value != 0 && !_resetInProgress, )) .toList(), selected: _application != null ? {_application!} : {}, From a6646b30d227c4b193b86bd71fb2544763e1aa6f Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Mon, 12 Feb 2024 16:17:51 +0100 Subject: [PATCH 06/11] Use _currentStep instead --- lib/app/views/reset_dialog.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index e319844c..37e2e9ba 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -69,7 +69,6 @@ class _ResetDialogState extends ConsumerState { InteractionEvent? _interaction; int _currentStep = -1; final _totalSteps = 3; - bool _resetInProgress = false; String _getMessage() { final l10n = AppLocalizations.of(context)!; @@ -107,6 +106,7 @@ class _ResetDialogState extends ConsumerState { onCancel: switch (_application) { Capability.fido2 => _currentStep < 3 ? () { + _currentStep = -1; _subscription?.cancel(); } : null, @@ -118,7 +118,6 @@ class _ResetDialogState extends ConsumerState { onPressed: switch (_application) { Capability.fido2 => _subscription == null ? () async { - _resetInProgress = true; _subscription = ref .read( fidoStateProvider(widget.data.node.path).notifier) @@ -225,7 +224,7 @@ class _ResetDialogState extends ConsumerState { tooltip: !showLabels ? c.getDisplayName(l10n) : null, enabled: - enabled & c.value != 0 && !_resetInProgress, + enabled & c.value != 0 && (_currentStep == -1), )) .toList(), selected: _application != null ? {_application!} : {}, From ea8ff38f76fd81eef33f53559f4c768c3c40c047 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 12 Feb 2024 17:56:30 +0100 Subject: [PATCH 07/11] add missing enum values --- lib/piv/models.dart | 6 +++++- lib/piv/models.g.dart | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/piv/models.dart b/lib/piv/models.dart index 5076e3bd..116115ed 100644 --- a/lib/piv/models.dart +++ b/lib/piv/models.dart @@ -77,7 +77,11 @@ enum PinPolicy { @JsonValue(0x02) once, @JsonValue(0x03) - always; + always, + @JsonValue(0x04) + matchOnce, + @JsonValue(0x05) + matchAlways; const PinPolicy(); diff --git a/lib/piv/models.g.dart b/lib/piv/models.g.dart index 82f79391..ea52a86c 100644 --- a/lib/piv/models.g.dart +++ b/lib/piv/models.g.dart @@ -84,6 +84,8 @@ const _$PinPolicyEnumMap = { PinPolicy.never: 1, PinPolicy.once: 2, PinPolicy.always: 3, + PinPolicy.matchOnce: 4, + PinPolicy.matchAlways: 5, }; _$PivStateMetadataImpl _$$PivStateMetadataImplFromJson( From 09db1e2694c78688c1f78de260abe73fc1c6f659 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 13 Feb 2024 13:02:54 +0100 Subject: [PATCH 08/11] Update NEWS --- NEWS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NEWS b/NEWS index bb7b094f..77af8c1e 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,17 @@ +* Version 6.4.0 + ** 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. + ** 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) ** Add command line options: --hidden/--shown, --log-file FILE. ** Disable autocorrect in text fields. From 9797f321b48075cec647b406b17185a002839d2b Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 13 Feb 2024 13:07:07 +0100 Subject: [PATCH 09/11] Pin Python on Linux for now --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a3d66cd0..2a0a1a5d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -21,6 +21,7 @@ jobs: - name: Install dependencies run: | + echo "PYVER=3.12.1" >> $GITHUB_ENV # Remove once 3.12.2 is available from PPA export PYVER_MINOR=${PYVER%.*} echo "PYVER_MINOR: $PYVER_MINOR" apt-get update From 675eba042a23ced7a3ed5fdee87f2d2b63548f36 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 14 Feb 2024 12:48:57 +0100 Subject: [PATCH 10/11] Fix FIDO reset over NFC --- NEWS | 1 + helper/helper/fido.py | 9 +++++---- lib/app/views/reset_dialog.dart | 15 +++++++++++---- lib/version.dart | 2 +- pubspec.yaml | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 77af8c1e..ddd03913 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ ** 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. diff --git a/helper/helper/fido.py b/helper/helper/fido.py index 6d8435b4..2480ea01 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -124,10 +124,11 @@ class Ctap2Node(RpcNode): removed = False while not event.wait(0.5): try: - with dev.open_connection(FidoConnection): - if removed: - sleep(1.0) # Wait for the device to settle - return dev.open_connection(FidoConnection) + conn = dev.open_connection(FidoConnection) + if removed: + sleep(1.0) # Wait for the device to settle + return conn + conn.close() except CardConnectionException: pass # Expected, ignore except NoCardException: diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index 37e2e9ba..3f9a1598 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -68,12 +68,19 @@ class _ResetDialogState extends ConsumerState { StreamSubscription? _subscription; InteractionEvent? _interaction; 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() { final l10n = AppLocalizations.of(context)!; final nfc = widget.data.node.transport == Transport.nfc; - if (_currentStep == 3) { + if (_currentStep == _totalSteps) { return l10n.l_fido_app_reset; } return switch (_interaction) { @@ -104,7 +111,7 @@ class _ResetDialogState extends ConsumerState { title: Text(l10n.s_factory_reset), key: factoryResetCancel, onCancel: switch (_application) { - Capability.fido2 => _currentStep < 3 + Capability.fido2 => _currentStep < _totalSteps ? () { _currentStep = -1; _subscription?.cancel(); @@ -113,7 +120,7 @@ class _ResetDialogState extends ConsumerState { _ => null, }, actions: [ - if (_currentStep < 3) + if (_currentStep < _totalSteps) TextButton( onPressed: switch (_application) { Capability.fido2 => _subscription == null diff --git a/lib/version.dart b/lib/version.dart index 400c699c..8310e272 100755 --- a/lib/version.dart +++ b/lib/version.dart @@ -2,4 +2,4 @@ // This file is generated by running ./set-version.py const String version = '6.4.0'; -const int build = 60400; +const int build = 60401; diff --git a/pubspec.yaml b/pubspec.yaml index 1f285a6a..9754b380 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # DO NOT MANUALLY EDIT THIS! -version: 6.4.0+60400 +version: 6.4.0+60401 environment: sdk: '>=3.0.0 <4.0.0' From 0cf48ada1cdd9ef94d89597affa6884f66a35647 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 14 Feb 2024 17:01:50 +0100 Subject: [PATCH 11/11] Windows: Require elevation in reset dialog --- helper/helper/fido.py | 3 ++- lib/app/views/elevate_fido_buttons.dart | 2 +- lib/app/views/reset_dialog.dart | 33 +++++++++++++++++-------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/helper/helper/fido.py b/helper/helper/fido.py index 2480ea01..b78863cf 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -126,8 +126,9 @@ class Ctap2Node(RpcNode): try: conn = dev.open_connection(FidoConnection) if removed: + conn.close() sleep(1.0) # Wait for the device to settle - return conn + return dev.open_connection(FidoConnection) conn.close() except CardConnectionException: pass # Expected, ignore diff --git a/lib/app/views/elevate_fido_buttons.dart b/lib/app/views/elevate_fido_buttons.dart index 78257b07..23181dc7 100644 --- a/lib/app/views/elevate_fido_buttons.dart +++ b/lib/app/views/elevate_fido_buttons.dart @@ -43,7 +43,7 @@ class ElevateFidoButtons extends ConsumerWidget { duration: const Duration(seconds: 30)); try { if (await ref.read(rpcProvider).requireValue.elevate()) { - ref.invalidate(rpcProvider); + ref.invalidate(rpcStateProvider); } else { await ref.read(withContextProvider)((context) async => showMessage(context, l10n.s_permission_denied)); diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index 3f9a1598..6e4e2a3e 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -15,6 +15,7 @@ */ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -25,6 +26,7 @@ import '../../app/logging.dart'; import '../../core/models.dart'; import '../../core/state.dart'; import '../../desktop/models.dart'; +import '../../desktop/state.dart'; import '../../fido/models.dart'; import '../../fido/state.dart'; import '../../management/models.dart'; @@ -36,6 +38,7 @@ import '../features.dart' as features; import '../message.dart'; import '../models.dart'; import '../state.dart'; +import 'elevate_fido_buttons.dart'; import 'keys.dart'; final _log = Logger('fido.views.reset_dialog'); @@ -106,7 +109,12 @@ class _ResetDialogState extends ConsumerState { .contains(widget.data.info.formFactor); final globalReset = isBio && (supported & Capability.piv.value) != 0; final l10n = AppLocalizations.of(context)!; + double progress = _currentStep == -1 ? 0.0 : _currentStep / (_totalSteps); + final needsElevation = Platform.isWindows && + _application == Capability.fido2 && + !ref.watch(rpcStateProvider.select((state) => state.isAdmin)); + return ResponsiveDialog( title: Text(l10n.s_factory_reset), key: factoryResetCancel, @@ -256,16 +264,21 @@ class _ResetDialogState extends ConsumerState { .bodyMedium ?.copyWith(fontWeight: FontWeight.w700), ), - Text( - switch (_application) { - Capability.oath => l10n.p_warning_disable_credentials, - 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 (needsElevation) ...[ + Text(l10n.p_elevated_permissions_required), + const ElevateFidoButtons(), + ] else ...[ + Text( + switch (_application) { + Capability.oath => l10n.p_warning_disable_credentials, + 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) ...[ Text('${l10n.s_status}: ${_getMessage()}'), LinearProgressIndicator(value: progress)