From ea770cfa1821bc2e8eeadc10286a464bd61fd1af Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:45:07 +0530 Subject: [PATCH] Export auth codes --- lib/ui/settings/data/export_widget.dart | 90 ++++++++++++++++++++----- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/lib/ui/settings/data/export_widget.dart b/lib/ui/settings/data/export_widget.dart index 9402f848e6..f07fccc143 100644 --- a/lib/ui/settings/data/export_widget.dart +++ b/lib/ui/settings/data/export_widget.dart @@ -1,16 +1,22 @@ +import 'dart:convert'; import 'dart:io'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/models/export/ente.dart'; import 'package:ente_auth/services/local_authentication_service.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_sodium/flutter_sodium.dart'; +import 'package:logging/logging.dart'; import 'package:share_plus/share_plus.dart'; Future handleExportClick(BuildContext context) async { @@ -19,16 +25,12 @@ Future handleExportClick(BuildContext context) async { title: "Select export format", body: "Encrypted exports will be protected by a password of your choice.", buttons: [ - ButtonWidget( + const ButtonWidget( buttonType: ButtonType.primary, labelText: "Encrypted", isInAlert: true, buttonSize: ButtonSize.large, buttonAction: ButtonAction.first, - onTap: () async { - showShortToast(context, "Encrypted export"); - }, - // shouldShowSuccessConfirmation: true, ), const ButtonWidget( buttonType: ButtonType.secondary, @@ -37,14 +39,67 @@ Future handleExportClick(BuildContext context) async { isInAlert: true, buttonAction: ButtonAction.second, ), - ], ); if (result?.action != null && result!.action != ButtonAction.cancel) { - await _showExportWarningDialog(context); + if (result.action == ButtonAction.first) { + await _requestForEncryptionPassword(context); + } else { + await _showExportWarningDialog(context); + } } } +Future _requestForEncryptionPassword(BuildContext context, + {String? password,}) async { + await showTextInputDialog( + context, + title: "Password to encrypt export", + submitButtonLabel: "Export", + hintText: "Enter password", + isPasswordInput: true, + alwaysShowSuccessState: false, + onSubmit: (String password) async { + if (password.isEmpty || password.length < 4) { + showToast(context, "Password must be at least 4 characters long."); + Future.delayed(const Duration(seconds: 0), () { + _requestForEncryptionPassword(context, password: password); + }); + return; + } + if (password.isNotEmpty) { + try { + final kekSalt = CryptoUtil.getSaltToDeriveKey(); + final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( + utf8.encode(password) as Uint8List, + kekSalt, + ); + String exportPlainText = await _getAuthDataForExport(); + // Encrypt the key with this derived key + final encResult = await CryptoUtil.encryptChaCha( + utf8.encode(exportPlainText) as Uint8List, derivedKeyResult.key,); + final encContent = Sodium.bin2base64(encResult.encryptedData!); + final encNonce = Sodium.bin2base64(encResult.header!); + final EnteAuthExport data = EnteAuthExport( + version: 1, + encryptedData: encContent, + encryptionNonce: encNonce, + kdfParams: KDFParams( + memLimit: derivedKeyResult.memLimit, + opsLimit: derivedKeyResult.opsLimit, + salt: Sodium.bin2base64(kekSalt),), + ); + // get json value of data + _exportCodes(context, jsonEncode(data.toJson())); + } catch(e,s) { + Logger("ExportWidget").severe(e, s); + showToast(context, "Error while exporting codes."); + } + } + }, + ); +} + Future _showExportWarningDialog(BuildContext context) async { await showChoiceActionSheet( context, @@ -52,14 +107,15 @@ Future _showExportWarningDialog(BuildContext context) async { body: context.l10n.exportWarningDesc, isCritical: true, firstButtonOnTap: () async { - _exportCodes(context); + final data = await _getAuthDataForExport(); + await _exportCodes(context, data); }, secondButtonLabel: context.l10n.cancel, firstButtonLabel: context.l10n.iUnderStand, ); } -Future _exportCodes(BuildContext context) async { +Future _exportCodes(BuildContext context, String fileContent) async { final _codeFile = File( Configuration.instance.getTempDirectory() + "ente-authenticator-codes.txt", ); @@ -71,12 +127,7 @@ Future _exportCodes(BuildContext context) async { if (_codeFile.existsSync()) { await _codeFile.delete(); } - final codes = await CodeStore.instance.getAllCodes(); - String data = ""; - for (final code in codes) { - data += code.rawData + "\n"; - } - _codeFile.writeAsStringSync(data); + _codeFile.writeAsStringSync(fileContent); await Share.shareFiles([_codeFile.path]); Future.delayed(const Duration(seconds: 15), () async { if (_codeFile.existsSync()) { @@ -84,3 +135,12 @@ Future _exportCodes(BuildContext context) async { } }); } + +Future _getAuthDataForExport() async { + final codes = await CodeStore.instance.getAllCodes(); + String data = ""; + for (final code in codes) { + data += code.rawData + "\n"; + } + return data; +}