From 1cdc36bf891fc8a6da4e244e48d5bc74971be426 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 08:34:10 +0200 Subject: [PATCH 01/12] Bump Flutter dependencies. --- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 47 +++++++++++-------- .../flutter/generated_plugin_registrant.cc | 3 ++ windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 8dbd6990..7e11ab27 100755 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include @@ -14,6 +15,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_drop_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); + screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 6fb2a09a..01f60315 100755 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop + screen_retriever url_launcher_linux window_manager ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 51fbb7f2..f425048b 100755 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,12 +6,14 @@ import FlutterMacOS import Foundation import desktop_drop +import screen_retriever import shared_preferences_macos import url_launcher_macos import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) + ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 86bc4ec5..25bd8848 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,14 +70,14 @@ packages: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.9" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "2.1.11" build_runner_core: dependency: transitive description: @@ -98,7 +98,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.3.0" + version: "8.3.2" characters: dependency: transitive description: @@ -147,14 +147,14 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" cross_file: dependency: transitive description: name: cross_file url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.3+1" crypto: dependency: transitive description: @@ -168,7 +168,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" desktop_drop: dependency: "direct main" description: @@ -189,7 +189,7 @@ packages: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.0.0" file: dependency: transitive description: @@ -203,7 +203,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -227,7 +227,7 @@ packages: name: flutter_riverpod url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" flutter_test: dependency: "direct dev" description: flutter @@ -244,7 +244,7 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.3+1" freezed_annotation: dependency: "direct main" description: @@ -258,7 +258,7 @@ packages: name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -291,7 +291,7 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" integration_test: dependency: "direct dev" description: flutter @@ -387,7 +387,7 @@ packages: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7" path_provider_platform_interface: dependency: transitive description: @@ -401,14 +401,14 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.0" pigeon: dependency: "direct dev" description: name: pigeon url: "https://pub.dartlang.org" source: hosted - version: "3.0.3" + version: "3.1.5" platform: dependency: transitive description: @@ -465,6 +465,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" shared_preferences: dependency: "direct main" description: @@ -637,7 +644,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.2" + version: "6.1.3" url_launcher_android: dependency: transitive description: @@ -728,14 +735,14 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.5.2" + version: "2.7.0" window_manager: dependency: "direct main" description: name: window_manager url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.5" xdg_directories: dependency: transitive description: @@ -749,7 +756,7 @@ packages: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" sdks: dart: ">=2.17.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index cf8e35de..d35fb0a0 100755 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,12 +7,15 @@ #include "generated_plugin_registrant.h" #include +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { DesktopDropPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopDropPlugin")); + ScreenRetrieverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 27cf8f20..0d6d49e8 100755 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop + screen_retriever url_launcher_windows window_manager ) From 69350df53c392bb621b725e3112819683ab65122 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 08:35:03 +0200 Subject: [PATCH 02/12] Set version. --- helper/version_info.txt | 4 ++-- lib/version.dart | 2 +- windows/runner/Runner.rc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index cd10b135..5f42a958 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.0.0-alpha.3'), + StringStruct('FileVersion', '6.0.0-beta.1'), StringStruct('LegalCopyright', 'Copyright (c) 2022 Yubico AB'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '6.0.0-alpha.3')]) + StringStruct('ProductVersion', '6.0.0-beta.1')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index 1ff0cb11..15507d88 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.0.0-alpha.3'; +const String version = '6.0.0-beta.1'; const int build = 59900; diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 7f853fdc..073d9dc0 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -69,7 +69,7 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else -#define VERSION_AS_STRING "6.0.0-alpha.3" +#define VERSION_AS_STRING "6.0.0-beta.1" #endif VS_VERSION_INFO VERSIONINFO From 36ab08edd38ec9997ecbd58e16214ad5514fbfc2 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 08:52:02 +0200 Subject: [PATCH 03/12] Add updated Podfile.lock. --- macos/Podfile.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 8a95613c..56f4a1a9 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,6 +2,8 @@ PODS: - desktop_drop (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) + - screen_retriever (0.0.1): + - FlutterMacOS - shared_preferences_macos (0.0.1): - FlutterMacOS - url_launcher_macos (0.0.1): @@ -12,6 +14,7 @@ PODS: DEPENDENCIES: - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) @@ -21,6 +24,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos FlutterMacOS: :path: Flutter/ephemeral + screen_retriever: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos shared_preferences_macos: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos url_launcher_macos: @@ -31,6 +36,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 + screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727 url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 From 8cb95e9b26aaa6b013200a336a6cc6c3426b973f Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 13:00:58 +0200 Subject: [PATCH 04/12] Adds a fallback using gnome-screenshot for screen capture. --- helper/helper/qr.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/helper/helper/qr.py b/helper/helper/qr.py index efa02511..fb430ecc 100644 --- a/helper/helper/qr.py +++ b/helper/helper/qr.py @@ -2,19 +2,42 @@ import mss import zxingcpp import base64 import io +import os +import sys +import subprocess +import tempfile +from mss.exception import ScreenShotError from PIL import Image +def _capture_screen(): + try: + with mss.mss() as sct: + monitor = sct.monitors[0] # 0 is the special "all monitors" value. + sct_img = sct.grab(monitor) # mss format + return Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX") + except ScreenShotError: + # One common error is that mss doesn't work with Wayland + if sys.platform.startswith("linux"): + # Try gnome-screenshot fallback + fd, fname = tempfile.mkstemp(suffix=".png") + try: + rc = subprocess.call(["gnome-screenshot", "-f", fname]) # nosec + if rc == 0: + return Image.open(fname) + finally: + os.unlink(fname) + raise ValueError("Unable to capture screenshot") + + def scan_qr(image_data=None): if image_data: msg = base64.b64decode(image_data) buf = io.BytesIO(msg) img = Image.open(buf) else: - with mss.mss() as sct: - monitor = sct.monitors[0] # 0 is the special "all monitors" value. - sct_img = sct.grab(monitor) # mss format - img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX") + img = _capture_screen() + result = zxingcpp.read_barcode(img) if result.valid: return result.text From 56a6946164c29f2a8c0f4b970ece9a23485f5652 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 15:43:32 +0200 Subject: [PATCH 05/12] Correctly handle PC/SC service unavailable. --- helper/helper/device.py | 6 +++--- lib/app/views/app_failure_page.dart | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helper/helper/device.py b/helper/helper/device.py index 2bd3dbeb..12d76f31 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -300,7 +300,7 @@ class UsbDeviceNode(AbstractDeviceNode): def ccid(self): try: return self._create_connection(SmartCardConnection) - except (ValueError, SmartcardException) as e: + except (ValueError, SmartcardException, EstablishContextException) as e: logger.warning("Error opening connection", exc_info=True) raise ConnectionException("ccid", e) @@ -335,7 +335,7 @@ class ReaderDeviceNode(AbstractDeviceNode): connection = self._device.open_connection(SmartCardConnection) info = read_info(connection) return ConnectionNode(self._device, connection, info) - except (ValueError, SmartcardException) as e: + except (ValueError, SmartcardException, EstablishContextException) as e: logger.warning("Error opening connection", exc_info=True) raise ConnectionException("ccid", e) @@ -346,7 +346,7 @@ class ReaderDeviceNode(AbstractDeviceNode): info = read_info(conn) connection = self._device.open_connection(FidoConnection) return ConnectionNode(self._device, connection, info) - except (ValueError, SmartcardException) as e: + except (ValueError, SmartcardException, EstablishContextException) as e: logger.warning("Error opening connection", exc_info=True) raise ConnectionException("fido", e) diff --git a/lib/app/views/app_failure_page.dart b/lib/app/views/app_failure_page.dart index aa4120eb..87dba8c0 100755 --- a/lib/app/views/app_failure_page.dart +++ b/lib/app/views/app_failure_page.dart @@ -32,7 +32,7 @@ class AppFailurePage extends ConsumerWidget { if (Platform.isMacOS) { message = 'Try to remove and re-insert your YubiKey.'; } else if (Platform.isLinux) { - message = 'Make sure pcscd is running.'; + message = 'Make sure pcscd is installed and running.'; } else { message = 'Make sure your smart card service is functioning.'; } From 5fa3275ebb06c1cba5b42f76105b6afe5fd89c21 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 16:45:26 +0200 Subject: [PATCH 06/12] Show better errors. --- helper/helper/oath.py | 8 ++- helper/helper/qr.py | 2 +- lib/app/state.dart | 6 +- lib/desktop/qr_scanner.dart | 2 +- lib/fido/views/add_fingerprint_dialog.dart | 41 +++++++++++--- lib/fido/views/pin_dialog.dart | 56 +++++++++++++------ lib/fido/views/rename_fingerprint_dialog.dart | 29 ++++++++-- lib/fido/views/reset_dialog.dart | 14 ++++- lib/oath/views/add_account_page.dart | 50 ++++++++++++++--- lib/oath/views/rename_account_dialog.dart | 38 ++++++++++--- 10 files changed, 196 insertions(+), 50 deletions(-) diff --git a/helper/helper/oath.py b/helper/helper/oath.py index 5aaccaea..499cd14b 100644 --- a/helper/helper/oath.py +++ b/helper/helper/oath.py @@ -278,7 +278,13 @@ class CredentialsNode(RpcNode): if data.get_id() in self._creds: raise ValueError("Credential already exists") - credential = self.session.put_credential(data, require_touch) + try: + credential = self.session.put_credential(data, require_touch) + except ApduError as e: + if e.sw == SW.INCORRECT_PARAMETERS: + raise ValueError("Issuer/name too long") + raise e + self._creds[credential.id] = credential return asdict(credential) diff --git a/helper/helper/qr.py b/helper/helper/qr.py index fb430ecc..905de620 100644 --- a/helper/helper/qr.py +++ b/helper/helper/qr.py @@ -41,4 +41,4 @@ def scan_qr(image_data=None): result = zxingcpp.read_barcode(img) if result.valid: return result.text - raise ValueError("Unable to read QR code") + return None diff --git a/lib/app/state.dart b/lib/app/state.dart index 387f5520..c772f7a5 100755 --- a/lib/app/state.dart +++ b/lib/app/state.dart @@ -106,7 +106,11 @@ class CurrentAppNotifier extends StateNotifier { } abstract class QrScanner { - Future scanQr([String? imageData]); + /// Scans (or searches the given image) for a QR code, and decodes it. + /// + /// The contained data is returned as a String, or null, if no QR code is + /// found. + Future scanQr([String? imageData]); } final qrScannerProvider = Provider( diff --git a/lib/desktop/qr_scanner.dart b/lib/desktop/qr_scanner.dart index fe3b2ee8..9f921f9b 100755 --- a/lib/desktop/qr_scanner.dart +++ b/lib/desktop/qr_scanner.dart @@ -9,7 +9,7 @@ class RpcQrScanner implements QrScanner { RpcQrScanner(this._rpc); @override - Future scanQr([String? imageData]) async { + Future scanQr([String? imageData]) async { final result = await _rpc.command('qr', [], params: {'image': imageData}); return result['result']; } diff --git a/lib/fido/views/add_fingerprint_dialog.dart b/lib/fido/views/add_fingerprint_dialog.dart index 4d7a8288..0ce0e8b9 100755 --- a/lib/fido/views/add_fingerprint_dialog.dart +++ b/lib/fido/views/add_fingerprint_dialog.dart @@ -8,6 +8,7 @@ import 'package:logging/logging.dart'; import 'package:yubico_authenticator/app/logging.dart'; import '../../app/message.dart'; +import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; import '../state.dart'; import '../../fido/models.dart'; @@ -92,7 +93,18 @@ class _AddFingerprintDialogState extends ConsumerState }, onError: (error, stacktrace) { _log.error('Error adding fingerprint', error, stacktrace); Navigator.of(context).pop(); - showMessage(context, 'Error adding fingerprint'); + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (error is RpcError) { + errorMessage = error.message; + } else { + errorMessage = error.toString(); + } + showMessage( + context, + 'Error adding fingerprint: $errorMessage', + duration: const Duration(seconds: 4), + ); }); } @@ -108,12 +120,27 @@ class _AddFingerprintDialogState extends ConsumerState } void _submit() async { - await ref - .read(fingerprintProvider(widget.devicePath).notifier) - .renameFingerprint(_fingerprint!, _label); - if (!mounted) return; - Navigator.of(context).pop(true); - showMessage(context, 'Fingerprint added'); + try { + await ref + .read(fingerprintProvider(widget.devicePath).notifier) + .renameFingerprint(_fingerprint!, _label); + if (!mounted) return; + Navigator.of(context).pop(true); + showMessage(context, 'Fingerprint added'); + } catch (e) { + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Error setting name: $errorMessage', + duration: const Duration(seconds: 4), + ); + } } @override diff --git a/lib/fido/views/pin_dialog.dart b/lib/fido/views/pin_dialog.dart index 96cc2b43..babd46e2 100755 --- a/lib/fido/views/pin_dialog.dart +++ b/lib/fido/views/pin_dialog.dart @@ -1,12 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; +import '../../app/logging.dart'; import '../../app/message.dart'; import '../../app/models.dart'; +import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; import '../models.dart'; import '../state.dart'; +final _log = Logger('fido.views.pin_dialog'); + class FidoPinDialog extends ConsumerStatefulWidget { final DevicePath devicePath; final FidoState state; @@ -68,6 +73,7 @@ class _FidoPinDialogState extends ConsumerState { ], Text( 'Enter your new PIN. A PIN must be at least $minPinLength characters long and may contain letters, numbers and special characters.'), + // TODO: Set max characters based on UTF-8 bytes TextFormField( initialValue: _newPin, autofocus: !hasPin, @@ -128,23 +134,39 @@ class _FidoPinDialogState extends ConsumerState { }); return; } - final result = await ref - .read(fidoStateProvider(widget.devicePath).notifier) - .setPin(_newPin, oldPin: oldPin); - result.when(success: () { - Navigator.of(context).pop(true); - showMessage(context, 'PIN set'); - }, failed: (retries, authBlocked) { - setState(() { - if (authBlocked) { - _currentPinError = - 'PIN has been blocked until the YubiKey is removed and reinserted'; - _currentIsWrong = true; - } else { - _currentPinError = 'Wrong PIN ($retries tries remaining)'; - _currentIsWrong = true; - } + try { + final result = await ref + .read(fidoStateProvider(widget.devicePath).notifier) + .setPin(_newPin, oldPin: oldPin); + result.when(success: () { + Navigator.of(context).pop(true); + showMessage(context, 'PIN set'); + }, failed: (retries, authBlocked) { + setState(() { + if (authBlocked) { + _currentPinError = + 'PIN has been blocked until the YubiKey is removed and reinserted'; + _currentIsWrong = true; + } else { + _currentPinError = 'Wrong PIN ($retries tries remaining)'; + _currentIsWrong = true; + } + }); }); - }); + } catch (e) { + _log.error('Failed to set PIN', e); + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Failed to set PIN: $errorMessage', + duration: const Duration(seconds: 4), + ); + } } } diff --git a/lib/fido/views/rename_fingerprint_dialog.dart b/lib/fido/views/rename_fingerprint_dialog.dart index 81e9a098..c493b48f 100755 --- a/lib/fido/views/rename_fingerprint_dialog.dart +++ b/lib/fido/views/rename_fingerprint_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/message.dart'; +import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; import '../models.dart'; import '../state.dart'; @@ -28,12 +29,27 @@ class _RenameAccountDialogState extends ConsumerState { } _submit() async { - final renamed = await ref - .read(fingerprintProvider(widget.devicePath).notifier) - .renameFingerprint(widget.fingerprint, _label); - if (!mounted) return; - Navigator.of(context).pop(renamed); - showMessage(context, 'Fingerprint renamed'); + try { + final renamed = await ref + .read(fingerprintProvider(widget.devicePath).notifier) + .renameFingerprint(widget.fingerprint, _label); + if (!mounted) return; + Navigator.of(context).pop(renamed); + showMessage(context, 'Fingerprint renamed'); + } catch (e) { + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Error renaming: $errorMessage', + duration: const Duration(seconds: 4), + ); + } } @override @@ -53,6 +69,7 @@ class _RenameAccountDialogState extends ConsumerState { const Text('This will change the label of the fingerprint.'), TextFormField( initialValue: _label, + // TODO: Make this field count UTF-8 bytes instead of characters. maxLength: 15, decoration: const InputDecoration( border: OutlineInputBorder(), diff --git a/lib/fido/views/reset_dialog.dart b/lib/fido/views/reset_dialog.dart index bb5942d9..e5a44f84 100755 --- a/lib/fido/views/reset_dialog.dart +++ b/lib/fido/views/reset_dialog.dart @@ -7,6 +7,7 @@ import 'package:yubico_authenticator/app/logging.dart'; import '../../app/message.dart'; import '../../core/models.dart'; +import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; import '../state.dart'; import '../../fido/models.dart'; @@ -69,7 +70,18 @@ class _ResetDialogState extends ConsumerState { }, onError: (e) { _log.error('Error performing FIDO reset', e); Navigator.of(context).pop(); - showMessage(context, 'Error performing reset'); + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Error performing reset: $errorMessage', + duration: const Duration(seconds: 4), + ); }); } : null, diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 8b42731a..bf6d272d 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -10,6 +10,7 @@ import 'package:yubico_authenticator/app/logging.dart'; import '../../app/message.dart'; import '../../app/models.dart'; import '../../app/state.dart'; +import '../../desktop/models.dart'; import '../../widgets/file_drop_target.dart'; import '../../widgets/responsive_dialog.dart'; import '../models.dart'; @@ -56,11 +57,30 @@ class _OathAddAccountPageState extends ConsumerState { _qrState = _QrScanState.scanning; }); final otpauth = await qrScanner.scanQr(); - final data = CredentialData.fromUri(Uri.parse(otpauth)); - _loadCredentialData(data); - } catch (e) { - setState(() { + if (otpauth == null) { + if (!mounted) return; showMessage(context, 'No QR code found'); + setState(() { + _qrState = _QrScanState.failed; + }); + } else { + final data = CredentialData.fromUri(Uri.parse(otpauth)); + _loadCredentialData(data); + } + } catch (e) { + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Failed reading QR code: $errorMessage', + duration: const Duration(seconds: 4), + ); + setState(() { _qrState = _QrScanState.failed; }); } @@ -139,7 +159,18 @@ class _OathAddAccountPageState extends ConsumerState { showMessage(context, 'Account added'); } catch (e) { _log.error('Failed to add account', e); - showMessage(context, 'Failed adding account'); + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Failed adding account: $errorMessage', + duration: const Duration(seconds: 4), + ); } } else { setState(() { @@ -161,8 +192,13 @@ class _OathAddAccountPageState extends ConsumerState { if (qrScanner != null) { final b64Image = base64Encode(fileData); final otpauth = await qrScanner.scanQr(b64Image); - final data = CredentialData.fromUri(Uri.parse(otpauth)); - _loadCredentialData(data); + if (otpauth == null) { + if (!mounted) return; + showMessage(context, 'No QR code found'); + } else { + final data = CredentialData.fromUri(Uri.parse(otpauth)); + _loadCredentialData(data); + } } }, child: Column( diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index 3ccb5de0..c695a091 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -1,13 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; +import '../../app/logging.dart'; import '../../app/message.dart'; +import '../../app/models.dart'; +import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; import '../models.dart'; import '../state.dart'; -import '../../app/models.dart'; import 'utils.dart'; +final _log = Logger('oath.view.rename_account_dialog'); + class RenameAccountDialog extends ConsumerStatefulWidget { final DeviceNode device; final OathCredential credential; @@ -53,13 +58,30 @@ class _RenameAccountDialogState extends ConsumerState { TextButton( onPressed: isValid ? () async { - final renamed = await ref - .read(credentialListProvider(widget.device.path).notifier) - .renameAccount(credential, - _issuer.isNotEmpty ? _issuer : null, _account); - if (!mounted) return; - Navigator.of(context).pop(renamed); - showMessage(context, 'Account renamed'); + try { + final renamed = await ref + .read( + credentialListProvider(widget.device.path).notifier) + .renameAccount(credential, + _issuer.isNotEmpty ? _issuer : null, _account); + if (!mounted) return; + Navigator.of(context).pop(renamed); + showMessage(context, 'Account renamed'); + } catch (e) { + _log.error('Failed to add account', e); + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + showMessage( + context, + 'Failed adding account: $errorMessage', + duration: const Duration(seconds: 4), + ); + } } : null, child: const Text('Save'), From c0674447e905792e50552a3bd164515157d8daf4 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 13 Jun 2022 16:49:53 +0200 Subject: [PATCH 07/12] N attempts -> N attempt(s). --- lib/fido/views/locked_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fido/views/locked_page.dart b/lib/fido/views/locked_page.dart index c7a2c6f0..b446805b 100755 --- a/lib/fido/views/locked_page.dart +++ b/lib/fido/views/locked_page.dart @@ -151,7 +151,7 @@ class _PinEntryFormState extends ConsumerState<_PinEntryForm> { return 'PIN temporarily blocked, remove and reinsert your YubiKey.'; } if (_retries != null) { - return 'Wrong PIN. $_retries attempts remaining.'; + return 'Wrong PIN. $_retries attempt(s) remaining.'; } return null; } From cdc111eca99da3102aa281d0bbf7677210db8d5c Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 14 Jun 2022 09:06:02 +0200 Subject: [PATCH 08/12] Better error message when gnome-screenshot is missing. --- helper/helper/qr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helper/helper/qr.py b/helper/helper/qr.py index 905de620..ce84b941 100644 --- a/helper/helper/qr.py +++ b/helper/helper/qr.py @@ -25,6 +25,8 @@ def _capture_screen(): rc = subprocess.call(["gnome-screenshot", "-f", fname]) # nosec if rc == 0: return Image.open(fname) + except FileNotFoundError: + pass # Fall through to ValueError finally: os.unlink(fname) raise ValueError("Unable to capture screenshot") From 8f932b3af01bee66bd0b0d5226afff32bc0bc6e7 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 14 Jun 2022 09:13:44 +0200 Subject: [PATCH 09/12] Fix Windows license generation. --- build-helper.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-helper.bat b/build-helper.bat index 5c42301d..bbbe9376 100644 --- a/build-helper.bat +++ b/build-helper.bat @@ -10,8 +10,8 @@ echo Generating license files... rmdir /s /q ..\build\windows\helper-license-venv poetry build poetry run python -m venv ..\build\windows\helper-license-venv -..\build\windows\helper-license-venv\Scripts\pip install --upgrade pip wheel -..\build\windows\helper-license-venv\Scripts\pip install dist\authenticator_helper-0.1.0-py3-none-any.whl pip-licenses +..\build\windows\helper-license-venv\Scripts\python -m pip install --upgrade pip wheel +..\build\windows\helper-license-venv\Scripts\python -m pip install dist\authenticator_helper-0.1.0-py3-none-any.whl pip-licenses ..\build\windows\helper-license-venv\Scripts\pip-licenses --format=json --no-license-path --with-license-file --ignore-packages authenticator-helper --output-file ..\assets\licenses\helper.json cd .. From 59f54b6e69e63d6596e9a292d6237cc5fb9cca23 Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Tue, 14 Jun 2022 11:02:42 +0200 Subject: [PATCH 10/12] Instructions for Linux --- .github/workflows/linux.yml | 1 + resources/README.adoc | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 resources/README.adoc diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7fdf95cb..8a481059 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -56,6 +56,7 @@ jobs: mkdir deploy mv resources/com.yubico.authenticator.desktop build/linux/x64/release/bundle mv resources/icons/com.yubico.yubioath.png build/linux/x64/release/bundle + mv resources/README.adoc build/linux/x64/release/bundle tar -czf deploy/yubioath-desktop-${REF}-linux-${{matrix.os}}.tar.gz -C build/linux/x64/release/bundle . - name: Upload artifact diff --git a/resources/README.adoc b/resources/README.adoc new file mode 100644 index 00000000..560b6f02 --- /dev/null +++ b/resources/README.adoc @@ -0,0 +1,14 @@ +== Instructions for Linux + +To run Yubico Authenticator, execute the authenticator binary by double clicking or running it from command line: + + ./authenticator + +You will need to have pcscd installed and running for Yubico Authenticator to work. +On Ubuntu: + + sudo apt install pcscd + +Note that the QR scanning feature requires gnome-screenshot when using Wayland. + + sudo apt install gnome-screenshot \ No newline at end of file From e75793ae30ab0814bbb81e4b1810ebbb76269e3b Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Tue, 14 Jun 2022 16:26:35 +0200 Subject: [PATCH 11/12] Make sure the save button is disabled when no usb application is selected --- lib/management/views/management_screen.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/management/views/management_screen.dart b/lib/management/views/management_screen.dart index f8f80349..3213bbbc 100755 --- a/lib/management/views/management_screen.dart +++ b/lib/management/views/management_screen.dart @@ -250,10 +250,11 @@ class _ManagementScreenState extends ConsumerState { data: (info) { bool hasConfig = info.version.major > 4; if (hasConfig) { - canSave = !_mapEquals( - _enabled, - info.config.enabledCapabilities, - ); + canSave = _enabled[Transport.usb] != 0 && + !_mapEquals( + _enabled, + info.config.enabledCapabilities, + ); } else { canSave = _interfaces != 0 && _interfaces != From ba74f1434eed74d17705e6f78fc056a8eb6da6fd Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 15 Jun 2022 10:23:15 +0200 Subject: [PATCH 12/12] update version to 6.0.0-dev.0 --- helper/version_info.txt | 4 ++-- lib/version.dart | 2 +- windows/runner/Runner.rc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index 5f42a958..95827db7 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.0.0-beta.1'), + StringStruct('FileVersion', '6.0.0-dev.0'), StringStruct('LegalCopyright', 'Copyright (c) 2022 Yubico AB'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '6.0.0-beta.1')]) + StringStruct('ProductVersion', '6.0.0-dev.0')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index 15507d88..4ff7d7d9 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.0.0-beta.1'; +const String version = '6.0.0-dev.0'; const int build = 59900; diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 073d9dc0..c8a14d37 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -69,7 +69,7 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else -#define VERSION_AS_STRING "6.0.0-beta.1" +#define VERSION_AS_STRING "6.0.0-dev.0" #endif VS_VERSION_INFO VERSIONINFO