diff --git a/lib/src/encryption.dart b/lib/src/encryption.dart new file mode 100644 index 00000000..84b31769 --- /dev/null +++ b/lib/src/encryption.dart @@ -0,0 +1,24 @@ +import 'package:dart_pg/dart_pg.dart'; +import 'package:org_flutter/org_flutter.dart'; +import 'package:orgro/src/debug.dart'; + +Future> decrypt( + (List blocks, String password) args) async { + final (blocks, password) = args; + final result = []; + + for (final block in blocks) { + try { + final message = await OpenPGP.readMessage(block.toRfc4880()); + final decrypted = await OpenPGP.decrypt( + message, + passwords: [password], + ); + result.add(decrypted.literalData?.text); + } catch (e, s) { + result.add(null); + logError(e, s); + } + } + return result; +} diff --git a/lib/src/pages/document.dart b/lib/src/pages/document.dart index 84614f02..875221b1 100644 --- a/lib/src/pages/document.dart +++ b/lib/src/pages/document.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -16,6 +17,7 @@ import 'package:orgro/src/components/slidable_action.dart'; import 'package:orgro/src/components/view_settings.dart'; import 'package:orgro/src/data_source.dart'; import 'package:orgro/src/debug.dart'; +import 'package:orgro/src/encryption.dart'; import 'package:orgro/src/file_picker.dart'; import 'package:orgro/src/navigation.dart'; import 'package:orgro/src/preferences.dart'; @@ -300,6 +302,11 @@ class _DocumentPageState extends State { if (_dirty.value) _onDocChanged(_doc); }, ), + DecryptContentBanner( + visible: _askToDecrypt, + onAccept: _decryptContent, + onDeny: viewSettings.setDecryptPolicy, + ), _maybeConstrainWidth( context, child: SelectionArea( @@ -314,7 +321,8 @@ class _DocumentPageState extends State { child: switch (doc) { OrgDocument() => OrgDocumentWidget(doc, shrinkWrap: true), OrgSection() => - OrgSectionWidget(doc, root: true, shrinkWrap: true) + OrgSectionWidget(doc, root: true, shrinkWrap: true), + _ => throw Exception('Unexpected document type: $doc'), }, ), ), @@ -622,6 +630,60 @@ class _DocumentPageState extends State { if (result == true) navigator.pop(); } + + bool? get _hasEncryptedContent => + DocumentProvider.of(context).analysis.hasEncryptedContent; + + bool get _askToDecrypt => + _viewSettings.decryptPolicy == DecryptPolicy.ask && + _hasEncryptedContent == true && + !_askForDirectoryPermissions && + !_askPermissionToLoadRemoteImages && + !_askPermissionToSaveChanges; + + Future _decryptContent() async { + final blocks = []; + _doc.visit((block) { + blocks.add(block); + return true; + }); + final password = await showDialog( + context: context, + builder: (context) => const InputPasswordDialog(), + ); + if (password == null) return; + if (!mounted) return; + time('decrypt', () => compute(decrypt, (blocks, password))) + .then((decrypted) => Navigator.pop(context, decrypted)); + final result = await showDialog>( + context: context, + builder: (context) => const ProgressIndicatorDialog(), + ); + if (!mounted) return; + if (result == null) { + showErrorSnackBar(context, 'Decryption failed'); + return; + } + OrgTree newDoc = _doc; + for (final (i, plaintext) in result.indexed) { + if (plaintext == null) { + showErrorSnackBar(context, 'Decryption failed'); + continue; + } + final block = blocks[i]; + try { + final replacement = + OrgDecryptedContent.fromDecryptedResult(block, plaintext); + newDoc = + newDoc.editNode(block)!.replace(replacement).commit() as OrgTree; + } catch (e, s) { + logError(e, s); + showErrorSnackBar(context, 'Failed to parse plaintext'); + continue; + } + } + await _updateDocument(newDoc); + } } class _KeyboardShortcuts extends StatelessWidget { diff --git a/pubspec.lock b/pubspec.lock index 0506ceb6..8104b49f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + dart_pg: + dependency: "direct main" + description: + name: dart_pg + sha256: "07f082f30a4ba024c181fd57906ea487d6e826d9c3281e0d6289fc17212a47c7" + url: "https://pub.dev" + source: hosted + version: "1.1.5" dynamic_fonts: dependency: "direct main" description: @@ -146,6 +154,14 @@ packages: url: "https://github.com/amake/file_picker_writable.git" source: git version: "2.1.0+1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -310,6 +326,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" leak_tracker: dependency: transitive description: @@ -386,18 +410,18 @@ packages: dependency: "direct main" description: name: org_flutter - sha256: "0bb754da89f7528d0dd81ff61403dc61382728e470ccec0abf9fa07b440344af" + sha256: f99f7128594c5ddae8a345525073a8a3d6dee51f3378ae3bcac509d7db430393 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.2.0" org_parser: dependency: transitive description: name: org_parser - sha256: "5745d233316b3f8cf27ff9082987c8cdcec792c7e6d138f2203aed4a30a99238" + sha256: d73bbf50aa9765ce3a624422dc9c37020fb83a816d983a15f846ba310064eb83 url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.4.0" path: dependency: transitive description: @@ -478,6 +502,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + pinenacl: + dependency: transitive + description: + name: pinenacl + sha256: "3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327" + url: "https://pub.dev" + source: hosted + version: "0.5.1" platform: dependency: transitive description: @@ -494,6 +526,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.7" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" quiver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 450dbd71..e5e403a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ environment: dependencies: cached_network_image: ^3.0.0 + dart_pg: ^1.1.5 dynamic_fonts: ^2.2.0 # dynamic_fonts: # path: ../dynamic-fonts-flutter @@ -35,7 +36,7 @@ dependencies: google_fonts: ^6.0.0 http: ^1.1.0 intl: ^0.18.0 - org_flutter: ^5.1.0 + org_flutter: ^5.2.0 # org_flutter: # path: ../org_flutter path_provider: ^2.0.9