diff --git a/.vscode/settings.json b/.vscode/settings.json index 06dd43543..0036191d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "activityBar.background": "#44116A", "titleBar.activeBackground": "#5F1895", "titleBar.activeForeground": "#FDFBFE" - } + }, + "java.configuration.updateBuildConfiguration": "interactive" } diff --git a/android/app/build.gradle b/android/app/build.gradle index 635ec19ee..81426f668 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -54,14 +54,14 @@ android { multiDexEnabled true } - signingConfigs { - release { - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH")) - keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS") - keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD") - storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD") - } - } + // signingConfigs { + // release { + // storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH")) + // keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS") + // keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD") + // storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD") + // } + // } flavorDimensions "default" productFlavors { @@ -79,7 +79,7 @@ android { buildTypes { release { - signingConfig signingConfigs.release + signingConfig signingConfigs.debug } debug { applicationIdSuffix '.debug' diff --git a/lib/ui/settings/data/import/google_auth_image_import.dart b/lib/ui/settings/data/import/google_auth_image_import.dart new file mode 100644 index 000000000..2a9163d79 --- /dev/null +++ b/lib/ui/settings/data/import/google_auth_image_import.dart @@ -0,0 +1,133 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:base32/base32.dart'; +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/models/protos/googleauth.pb.dart'; +import 'package:ente_auth/services/authenticator_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/ui/settings/data/import/analyze_qr_code.dart'; +import 'package:ente_auth/ui/settings/data/import/import_success.dart'; +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; + +const kGoogleAuthExportPrefix = 'otpauth-migration://offline?data='; + +Future showGoogleAuthImageInstruction(BuildContext context) async { + final l10n = context.l10n; + final result = await showDialogWidget( + context: context, + title: l10n.importFromApp("Google Authenticator"), + body: l10n.importGoogleAuthGuide, + buttons: [ + ButtonWidget( + buttonType: ButtonType.primary, + labelText: l10n.scanAQrCode, + isInAlert: true, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.first, + ), + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.l10n.cancel, + buttonSize: ButtonSize.large, + isInAlert: true, + buttonAction: ButtonAction.second, + ), + ], + ); + if (result?.action != null && result!.action != ButtonAction.cancel) { + if (result.action == ButtonAction.first) { + final List? codes = await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return const QrScanner(); + }, + ), + ); + if (codes == null || codes.isEmpty) { + return; + } + for (final code in codes) { + await CodeStore.instance.addCode(code, shouldSync: false); + } + unawaited(AuthenticatorService.instance.onlineSync()); + importSuccessDialog(context, codes.length); + } + } +} + +List parseGoogleAuth(String qrCodeData) { + try { + List codes = []; + final String payload = qrCodeData.substring(kGoogleAuthExportPrefix.length); + final Uint8List base64Decoded = base64Decode(Uri.decodeComponent(payload)); + final MigrationPayload mPayload = + MigrationPayload.fromBuffer(base64Decoded); + for (var otpParameter in mPayload.otpParameters) { + // Build the OTP URL + String otpUrl; + String issuer = otpParameter.issuer; + String account = otpParameter.name; + var counter = otpParameter.counter; + // Create a list of bytes from the list of integers. + Uint8List bytes = Uint8List.fromList(otpParameter.secret); + + // Encode the bytes to base 32. + String base32String = base32.encode(bytes); + String secret = base32String; + // identify digit count + int digits = 6; + int timer = 30; // default timer, no field in Google Auth + Algorithm algorithm = Algorithm.sha1; + switch (otpParameter.algorithm) { + case MigrationPayload_Algorithm.ALGORITHM_MD5: + throw Exception('GoogleAuthImport: MD5 is not supported'); + case MigrationPayload_Algorithm.ALGORITHM_SHA1: + algorithm = Algorithm.sha1; + break; + case MigrationPayload_Algorithm.ALGORITHM_SHA256: + algorithm = Algorithm.sha256; + break; + case MigrationPayload_Algorithm.ALGORITHM_SHA512: + algorithm = Algorithm.sha512; + break; + case MigrationPayload_Algorithm.ALGORITHM_UNSPECIFIED: + algorithm = Algorithm.sha1; + break; + } + switch (otpParameter.digits) { + case MigrationPayload_DigitCount.DIGIT_COUNT_EIGHT: + digits = 8; + break; + case MigrationPayload_DigitCount.DIGIT_COUNT_SIX: + digits = 6; + break; + case MigrationPayload_DigitCount.DIGIT_COUNT_UNSPECIFIED: + digits = 6; + } + + if (otpParameter.type == MigrationPayload_OtpType.OTP_TYPE_TOTP || + otpParameter.type == MigrationPayload_OtpType.OTP_TYPE_UNSPECIFIED) { + otpUrl = + 'otpauth://totp/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer'; + } else if (otpParameter.type == MigrationPayload_OtpType.OTP_TYPE_HOTP) { + otpUrl = + 'otpauth://hotp/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter'; + } else { + throw Exception('Invalid OTP type'); + } + codes.add(Code.fromRawData(otpUrl)); + } + return codes; + } catch (e, s) { + Logger("GoogleAuthImport") + .severe("Error while parsing Google Auth QR code", e, s); + throw Exception('Failed to parse Google Auth QR code \n ${e.toString()}'); + } +} diff --git a/lib/ui/settings/data/import/import_service.dart b/lib/ui/settings/data/import/import_service.dart index 514eb9362..53c7597bd 100644 --- a/lib/ui/settings/data/import/import_service.dart +++ b/lib/ui/settings/data/import/import_service.dart @@ -1,6 +1,6 @@ import 'package:ente_auth/ui/settings/data/import/aegis_import.dart'; -import 'package:ente_auth/ui/settings/data/import/analyze_qr_code.dart'; import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart'; +import 'package:ente_auth/ui/settings/data/import/google_auth_image_import.dart'; import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart'; import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart'; import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart'; @@ -33,13 +33,7 @@ class ImportService { showAegisImportInstruction(context); break; case ImportType.googleAuthenticatorImage: - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return const QrScanner(); - }, - ), - ); + showGoogleAuthImageInstruction(context); break; } }