diff --git a/README.md b/README.md index e507b52..bef9303 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ This app is a modern, Flutter based GUI on top of Dell Command | Configure CLI, * Integrated OTA via Github API * Detects and handles unsupported modes on supported machines * Detects non-dell machines, shows error message +* Support protected BIOS (System/Setup/Owner passwords), and secure key saving Control features: * Battery status overview (health etc.) @@ -83,5 +84,8 @@ Please see [issues](https://github.com/alexVinarskis/dell-powermanager/issues). * Dell for providing 'Dell Command | Configure CLI' * Google for creating Flutter :) +## Disclaimer +As per license, this software is provided as-is, without any warranty. It is not affiliated with Dell in any way. Use at your own risk. Me or any other contributors are not responsible for any damage caused by this software, including but not limited to data loss, hardware damage, data breaches etc. Where applicable, integrated solution for secure key saving is used, but it is not guaranteed to be secure in any way. Understand risk and implications before using it. No legal claims can be made against the author or contributors. + ## License This application is licensed under GPLv3. In short, this means you use/copy/modify/distribute it for free, but you must provide source code of your modifications, and keep the same license. You cannot sell it as proprietary software. See [LICENSE](LICENSE) for details. diff --git a/lib/classes/api_cctk.dart b/lib/classes/api_cctk.dart index eb7151d..9f917a2 100644 --- a/lib/classes/api_cctk.dart +++ b/lib/classes/api_cctk.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:dell_powermanager/classes/bios_protection_manager.dart'; import 'package:process_run/shell.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -43,6 +44,7 @@ class ApiCCTK { ApiCCTK(Duration refreshInternal) { sourceEnvironment(); + BiosProtectionManager.secureReadPassword(); _refreshInternal = refreshInternal; _query(); _timer = Timer.periodic(_refreshInternal, (Timer t) => _query()); diff --git a/lib/classes/bios_protection_manager.dart b/lib/classes/bios_protection_manager.dart index 62bb0ec..5532c5a 100644 --- a/lib/classes/bios_protection_manager.dart +++ b/lib/classes/bios_protection_manager.dart @@ -1,8 +1,33 @@ -import 'package:dell_powermanager/classes/api_cctk.dart'; +import 'dart:convert'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +import '../classes/api_cctk.dart'; +import '../configs/constants.dart'; import '../configs/environment.dart'; class BiosProtectionManager { + static const storage = FlutterSecureStorage(); + + static Future secureReadPassword() async { + String? pwdB64 = await storage.read(key: Constants.varnameBiosPwd); + if (pwdB64 == null) { + return; + } + String pwd = utf8.decode(base64.decode(pwdB64)); + if (pwd.isEmpty) { + return; + } + loadPassword(pwd); + } + + static Future secureWritePassword(String password) async { + await storage.write( + key: Constants.varnameBiosPwd, + value: base64.encode(utf8.encode(password)), + ); + loadPassword(password); + } static void loadPassword(String password) { Environment.biosPwd = password; diff --git a/lib/components/notification_bios_protection.dart b/lib/components/notification_bios_protection.dart index 156d95c..9ee36f9 100644 --- a/lib/components/notification_bios_protection.dart +++ b/lib/components/notification_bios_protection.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'dart:io'; import 'package:dell_powermanager/classes/cctk.dart'; import 'package:dell_powermanager/components/notification_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:passwordfield/passwordfield.dart'; import '../classes/api_cctk.dart'; import '../classes/bios_protection_manager.dart'; @@ -43,6 +45,7 @@ class NotificationBiosProtectionState extends State late Map biosProtectionStateSubtitles; final FocusNode modalButtonFocusNode = FocusNode(); final TextEditingController modalPwdController = TextEditingController(); + bool _savingPwd = false; @override void initState() { @@ -87,6 +90,7 @@ class NotificationBiosProtectionState extends State /* Ignore state, if issue was already detected */ if ( + _biosProtectionState == BiosProtectionState.unlockingSucceeded || _biosProtectionState == BiosProtectionState.unlockingSysPwdFailed || _biosProtectionState == BiosProtectionState.unlockingSetupPwdFailed || _biosProtectionState == BiosProtectionState.missingSetupPwd || @@ -127,52 +131,117 @@ class NotificationBiosProtectionState extends State return AlertDialog( title: Text(title), content: SizedBox( - width: 400, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "$p1\n", - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.justify, - ), - PasswordField( - color: Theme.of(context).colorScheme.primary, - passwordConstraint: r'^\S+$', - passwordDecoration: PasswordDecoration(), - controller: modalPwdController, - hintText: hint, - autoFocus: true, - onSubmit: (text) => { - modalButtonFocusNode.requestFocus(), - }, - border: PasswordBorder( - border: OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: Theme.of(context).colorScheme.primary, - ), - borderRadius: BorderRadius.circular(12), + width: 450, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + p1, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.justify, ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - width: 2, + Padding( + padding: const EdgeInsets.only(left: 5, right: 5, top: 30, bottom: 25), + child: PasswordField( color: Theme.of(context).colorScheme.primary, + passwordConstraint: r'^\S+$', + passwordDecoration: PasswordDecoration(), + controller: modalPwdController, + hintText: hint, + autoFocus: true, + onSubmit: (text) => { + modalButtonFocusNode.requestFocus(), + }, + border: PasswordBorder( + border: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: Theme.of(context).colorScheme.primary, + ), + borderRadius: BorderRadius.circular(14), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: Theme.of(context).colorScheme.primary, + ), + borderRadius: BorderRadius.circular(14), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: BorderSide( + width: 2, + color: Theme.of(context).colorScheme.error, + ), + ), + ), + errorMessage: S.of(context)!.biosProtectionAlertRequiredPwdErrorMsg, ), - borderRadius: BorderRadius.circular(12), ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - width: 2, - color: Theme.of(context).colorScheme.error, + Card( + clipBehavior: Clip.antiAlias, + color: Theme.of(context).colorScheme.surface.withOpacity(0.75), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14.0), ), + elevation: 0, + margin: const EdgeInsets.only(left: 5, right: 5), + child: InkWell( + onTap: () { + setState(() { + _savingPwd = !_savingPwd; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Checkbox( + value: _savingPwd, + onChanged: (bool? value) { + setState(() { + _savingPwd = value!; + }); + }, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 6), + alignment: Alignment.centerLeft, + child: Text( + S.of(context)!.biosProtectionAlertRequiredPwdSavePwdTitle, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.left, + ), + ), + Container( + width: 360, + padding: const EdgeInsets.symmetric(vertical: 6), + alignment: Alignment.centerLeft, + child: Text( + Platform.isLinux ? S.of(context)!.biosProtectionAlertRequiredPwdSavePwdDisclaimerLinux : S.of(context)!.biosProtectionAlertRequiredPwdSavePwdDisclaimerWindows, + textAlign: TextAlign.justify, + style: GoogleFonts.sourceCodePro().copyWith(color: Theme.of(context).textTheme.bodyMedium!.color!), + ), + ), + ], + ), + ], + ), + ), + ), ), - ), - errorMessage: S.of(context)!.biosProtectionAlertRequiredPwdErrorMsg, - ), - ], + ], + ); + }, ), ), actions: [ @@ -187,6 +256,9 @@ class NotificationBiosProtectionState extends State final String enteredPwd = modalPwdController.text; if (enteredPwd.isNotEmpty) { BiosProtectionManager.loadPassword(enteredPwd); + if (_savingPwd) { + BiosProtectionManager.secureWritePassword(enteredPwd); + } ApiCCTK.request(ApiCCTK.cctkState.exitStateWrite!.cctkType, ApiCCTK.cctkState.exitStateWrite!.mode); setState(() { _biosProtectionState = BiosProtectionState.unlocking; @@ -196,7 +268,7 @@ class NotificationBiosProtectionState extends State ), ], ); - } + }, ); } @@ -207,7 +279,7 @@ class NotificationBiosProtectionState extends State return AlertDialog( title: Text(S.of(context)!.biosProtectionAlertOwnerPwdTitle), content: SizedBox( - width: 400, + width: 450, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 648c086..c7d1891 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -112,6 +112,9 @@ "biosProtectionAlertRequiredPwdButton" : "OK", "biosProtectionAlertRequiredSysPwdP1" : "BIOS configuration is protected with `System Password`.\n\nTo alter settings, either remove password protection, or enter current password below:", "biosProtectionAlertRequiredSetupPwdP1" : "BIOS configuration is protected with `Setup Password`.\n\nTo alter settings, either remove password protection, or enter current password below:", + "biosProtectionAlertRequiredPwdSavePwdTitle" : "Save password", + "biosProtectionAlertRequiredPwdSavePwdDisclaimerLinux" : "Store password in system keyring via `libsecret`. It is secure, but not impossible to retrieve.\n\nUnderstand the risks before opting in.", + "biosProtectionAlertRequiredPwdSavePwdDisclaimerWindows" : "Store password. It is secure, but not impossible to retrieve.\n\nUnderstand the risks before opting in.", "cctkThermalOptimizedTitle" : "Optimized", "cctkThermalOptimizedDescription" : "This is the standard setting for cooling fan and processor heat management. This setting is a balance of performance., noise and temperature.", diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 7312ebb..bafef03 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_localization_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin"); flutter_localization_plugin_register_with_registrar(flutter_localization_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_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); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 819d5db..b11f05d 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_acrylic flutter_localization + flutter_secure_storage_linux screen_retriever url_launcher_linux window_manager diff --git a/package.sh b/package.sh index 6025dfa..a64c73f 100755 --- a/package.sh +++ b/package.sh @@ -53,7 +53,7 @@ sed -i "s|{NAME}|${NAME}|g" ./package/usr/local/share/applicatio PRIORITY="standard" MAINTAINER="alexVinarskis " HOMEPAGE="https://github.com/alexVinarskis/dell-powermanager" -DEPENDS="libgtk-3-0, libblkid1, liblzma5, curl, apt, tar, pkexec, power-profiles-daemon, bash" +DEPENDS="libgtk-3-0, libblkid1, liblzma5, curl, apt, tar, pkexec, bash, libsecret-1-0" DESCRIPTION="Cross-Platform Dell Power Manager re-implementation in Flutter" # Create control file of .deb diff --git a/pubspec.lock b/pubspec.lock index 961455b..ebc31c1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -123,6 +123,54 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + url: "https://pub.dev" + source: hosted + version: "3.0.1" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + url: "https://pub.dev" + source: hosted + version: "3.0.0" flutter_svg: dependency: "direct main" description: @@ -173,6 +221,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0ac11e4..400acac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: version: ^3.0.2 touch_interceptor: ^0.1.1 shared_preferences: ^2.2.2 + flutter_secure_storage: ^9.0.0 passwordfield: ^0.2.0 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 34e471e..0bcfcd5 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterAcrylicPlugin")); FlutterLocalizationPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index de66927..edf0879 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_acrylic flutter_localization + flutter_secure_storage_windows screen_retriever url_launcher_windows window_manager