Skip to content

Commit

Permalink
feat: option to secure save BIOS password
Browse files Browse the repository at this point in the history
Password would be encoded to base64 and stored in system keyring.
On linux, running it requires `libsecret-1-0`.

DISCLAIMER: Altough this is considered secured, it is not
unretreivable. Do at your own risk, understand how system
keyrings work before using. I do not hold any liability for
potential data breaches.
  • Loading branch information
alexVinarskis committed Feb 18, 2024
1 parent fb2c9d3 commit b7f9788
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 44 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down Expand Up @@ -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.
2 changes: 2 additions & 0 deletions lib/classes/api_cctk.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -43,6 +44,7 @@ class ApiCCTK {

ApiCCTK(Duration refreshInternal) {
sourceEnvironment();
BiosProtectionManager.secureReadPassword();
_refreshInternal = refreshInternal;
_query();
_timer = Timer.periodic(_refreshInternal, (Timer t) => _query());
Expand Down
27 changes: 26 additions & 1 deletion lib/classes/bios_protection_manager.dart
Original file line number Diff line number Diff line change
@@ -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<void> 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<void> 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;
Expand Down
156 changes: 114 additions & 42 deletions lib/components/notification_bios_protection.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -43,6 +45,7 @@ class NotificationBiosProtectionState extends State<NotificationBiosProtection>
late Map<BiosProtectionState, String> biosProtectionStateSubtitles;
final FocusNode modalButtonFocusNode = FocusNode();
final TextEditingController modalPwdController = TextEditingController();
bool _savingPwd = false;

@override
void initState() {
Expand Down Expand Up @@ -87,6 +90,7 @@ class NotificationBiosProtectionState extends State<NotificationBiosProtection>

/* Ignore state, if issue was already detected */
if (
_biosProtectionState == BiosProtectionState.unlockingSucceeded ||
_biosProtectionState == BiosProtectionState.unlockingSysPwdFailed ||
_biosProtectionState == BiosProtectionState.unlockingSetupPwdFailed ||
_biosProtectionState == BiosProtectionState.missingSetupPwd ||
Expand Down Expand Up @@ -127,52 +131,117 @@ class NotificationBiosProtectionState extends State<NotificationBiosProtection>
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: <Widget>[
Expand All @@ -187,6 +256,9 @@ class NotificationBiosProtectionState extends State<NotificationBiosProtection>
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;
Expand All @@ -196,7 +268,7 @@ class NotificationBiosProtectionState extends State<NotificationBiosProtection>
),
],
);
}
},
);
}

Expand All @@ -207,7 +279,7 @@ class NotificationBiosProtectionState extends State<NotificationBiosProtection>
return AlertDialog(
title: Text(S.of(context)!.biosProtectionAlertOwnerPwdTitle),
content: SizedBox(
width: 400,
width: 450,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
Expand Down
3 changes: 3 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
4 changes: 4 additions & 0 deletions linux/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <flutter_localization/flutter_localization_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_acrylic
flutter_localization
flutter_secure_storage_linux
screen_retriever
url_launcher_linux
window_manager
Expand Down
2 changes: 1 addition & 1 deletion package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ sed -i "s|{NAME}|${NAME}|g" ./package/usr/local/share/applicatio
PRIORITY="standard"
MAINTAINER="alexVinarskis <[email protected]>"
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
Expand Down
56 changes: 56 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit b7f9788

Please sign in to comment.