Переглянути джерело

Pull the key from server on sign in

Vishnu Mohandas 4 роки тому
батько
коміт
3d3c1496e7

+ 35 - 5
lib/core/configuration.dart

@@ -1,3 +1,6 @@
+import 'dart:convert';
+
+import 'package:crypto/crypto.dart';
 import 'dart:io' as io;
 import 'package:path_provider/path_provider.dart';
 import 'package:photos/utils/crypto_util.dart';
@@ -14,6 +17,9 @@ class Configuration {
   static const passwordKey = "password";
   static const hasOptedForE2EKey = "has_opted_for_e2e_encryption";
   static const keyKey = "key";
+  static const keyEncryptedKey = "encrypted_key";
+
+  static final String iv = base64.encode(List.filled(16, 0));
 
   SharedPreferences _preferences;
   String _documentsDirectory;
@@ -29,6 +35,15 @@ class Configuration {
     new io.Directory(_thumbnailsDirectory).createSync(recursive: true);
   }
 
+  Future<void> generateAndSaveKey(String passphrase) async {
+    final key = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
+    await setKey(key);
+    final hashedPassphrase = sha256.convert(passphrase.codeUnits);
+    final encryptedKey = CryptoUtil.encryptToBase64(
+        key, base64.encode(hashedPassphrase.bytes), iv);
+    await setEncryptedKey(encryptedKey);
+  }
+
   String getEndpoint() {
     return _preferences.getString(endpointKey);
   }
@@ -85,15 +100,30 @@ class Configuration {
     // return _preferences.getBool(hasOptedForE2EKey);
   }
 
-  Future<void> generateAndSaveKey(String passphrase) async {
-    final key = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
+  Future<void> setEncryptedKey(String encryptedKey) async {
+    await _preferences.setString(keyEncryptedKey, encryptedKey);
+  }
+
+  String getEncryptedKey() {
+    return _preferences.getString(keyEncryptedKey);
+  }
+
+  Future<void> setKey(String key) async {
     await _preferences.setString(keyKey, key);
   }
 
-  // TODO: Encrypt with a passphrase and store in secure storage
+  Future<void> decryptEncryptedKey(String passphrase) async {
+    final hashedPassphrase = sha256.convert(passphrase.codeUnits);
+    final encryptedKey = getEncryptedKey();
+    final key = CryptoUtil.decryptFromBase64(
+        encryptedKey, base64.encode(hashedPassphrase.bytes), iv);
+    await setKey(key);
+  }
+
+  // TODO: Store in secure storage
   String getKey() {
-    return "8qD++K3xkgjIl3dIsGiTze5PhYtxiS5AtOeZw+Bl1z0=";
-    // return _preferences.getString(keyKey);
+    // return "8qD++K3xkgjIl3dIsGiTze5PhYtxiS5AtOeZw+Bl1z0=";
+    return _preferences.getString(keyKey);
   }
 
   String getDocumentsDirectory() {

+ 12 - 5
lib/photo_sync_manager.dart

@@ -11,6 +11,7 @@ import 'package:photos/events/photo_upload_event.dart';
 import 'package:photos/events/user_authenticated_event.dart';
 import 'package:photos/file_repository.dart';
 import 'package:photo_manager/photo_manager.dart';
+import 'package:photos/models/file_type.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/file_name_util.dart';
 import 'package:photos/utils/file_util.dart';
@@ -201,6 +202,9 @@ class PhotoSyncManager {
       File file = photosToBeUploaded[i];
       _logger.info("Uploading " + file.toString());
       try {
+        if (file.fileType == FileType.video) {
+          continue;
+        }
         var uploadedFile;
         if (Configuration.instance.hasOptedForE2E()) {
           uploadedFile = await _uploadEncryptedFile(file);
@@ -279,10 +283,9 @@ class PhotoSyncManager {
         file.ownerID = json["ownerID"];
         file.updationTime = json["updationTime"];
         file.isEncrypted = true;
+        final String metadataIV = json["metadataIV"];
         Map<String, dynamic> metadata = jsonDecode(CryptoUtil.decryptFromBase64(
-            json["metadata"],
-            Configuration.instance.getKey(),
-            json["metadataIV"]));
+            json["metadata"], Configuration.instance.getKey(), metadataIV));
         file.applyMetadata(metadata);
         return file;
       }).toList();
@@ -312,8 +315,12 @@ class PhotoSyncManager {
     final metadata = jsonEncode(file.getMetadata());
     final metadataIV =
         CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
-    final encryptedMetadata =
-        CryptoUtil.encryptToBase64(metadata, key, metadataIV);
+    var encryptedMetadata;
+    try {
+      encryptedMetadata = CryptoUtil.encryptToBase64(metadata, key, metadataIV);
+    } catch (e, stacktrace) {
+      _logger.severe(e, stacktrace);
+    }
     final formData = FormData.fromMap({
       "file": MultipartFile.fromFileSync(encryptedFilePath,
           filename: encryptedFileName),

+ 29 - 18
lib/ui/sign_in_widget.dart

@@ -138,6 +138,15 @@ class _SignInWidgetState extends State<SignInWidget> {
                   .login(_usernameController.text, _passwordController.text);
               if (loggedIn) {
                 Navigator.of(context).pop();
+                if (Configuration.instance.getEncryptedKey() != null) {
+                  showDialog(
+                    context: context,
+                    barrierDismissible: false,
+                    builder: (BuildContext context) {
+                      return PassphraseDialog(false);
+                    },
+                  );
+                }
               } else {
                 _showAuthenticationFailedErrorDialog();
               }
@@ -276,7 +285,13 @@ class SelectEncryptionLevelWidget extends StatelessWidget {
           onPressed: () {
             Navigator.of(context).pop();
             Configuration.instance.setOptInForE2E(true);
-            _showEnterPassphraseDialog(context);
+            showDialog(
+              context: context,
+              barrierDismissible: false,
+              builder: (BuildContext context) {
+                return PassphraseDialog(true);
+              },
+            );
           },
         ),
         CupertinoDialogAction(
@@ -290,28 +305,21 @@ class SelectEncryptionLevelWidget extends StatelessWidget {
       ],
     );
   }
-
-  void _showEnterPassphraseDialog(BuildContext context) {
-    showDialog(
-      context: context,
-      barrierDismissible: false,
-      builder: (BuildContext context) {
-        return PassphraseWidget();
-      },
-    );
-  }
 }
 
-class PassphraseWidget extends StatefulWidget {
-  const PassphraseWidget({
+class PassphraseDialog extends StatefulWidget {
+  final bool isFreshRegistration;
+
+  const PassphraseDialog(
+    this.isFreshRegistration, {
     Key key,
   }) : super(key: key);
 
   @override
-  _PassphraseWidgetState createState() => _PassphraseWidgetState();
+  _PassphraseDialogState createState() => _PassphraseDialogState();
 }
 
-class _PassphraseWidgetState extends State<PassphraseWidget> {
+class _PassphraseDialogState extends State<PassphraseDialog> {
   String _passphrase = "";
 
   @override
@@ -322,8 +330,6 @@ class _PassphraseWidgetState extends State<PassphraseWidget> {
         padding: const EdgeInsets.fromLTRB(0, 16, 0, 0),
         child: Column(
           children: [
-            Text("Do not forget this passphrase!"),
-            Padding(padding: EdgeInsets.all(8)),
             CupertinoTextField(
               autofocus: true,
               style: Theme.of(context).textTheme.subtitle1,
@@ -340,7 +346,12 @@ class _PassphraseWidgetState extends State<PassphraseWidget> {
           child: Text('Save'),
           onPressed: () async {
             Navigator.of(context).pop();
-            await Configuration.instance.generateAndSaveKey(_passphrase);
+            if (widget.isFreshRegistration) {
+              await Configuration.instance.generateAndSaveKey(_passphrase);
+              await UserAuthenticator.instance.setEncryptedKeyOnServer();
+            } else {
+              await Configuration.instance.decryptEncryptedKey(_passphrase);
+            }
             Bus.instance.fire(UserAuthenticatedEvent());
           },
         )

+ 16 - 0
lib/user_authenticator.dart

@@ -56,10 +56,26 @@ class UserAuthenticator {
     });
   }
 
+  Future<void> setEncryptedKeyOnServer() {
+    return _dio.put(
+      Configuration.instance.getHttpEndpoint() + "/users/encrypted-key",
+      data: {
+        "encryptedKey": Configuration.instance.getEncryptedKey(),
+      },
+      options: Options(headers: {
+        "X-Auth-Token": Configuration.instance.getToken(),
+      }),
+    );
+  }
+
   void _saveConfiguration(String username, String password, Response response) {
     Configuration.instance.setUsername(username);
     Configuration.instance.setPassword(password);
     Configuration.instance.setUserID(response.data["id"]);
     Configuration.instance.setToken(response.data["token"]);
+    final String encryptedKey = response.data["encryptedKey"];
+    if (encryptedKey != null && encryptedKey.isNotEmpty) {
+      Configuration.instance.setEncryptedKey(encryptedKey);
+    }
   }
 }

+ 5 - 7
lib/utils/crypto_util.dart

@@ -15,7 +15,7 @@ class CryptoUtil {
     final encrypter = AES(Key.fromBase64(base64Key));
     return encrypter
         .encrypt(
-          Uint8List.fromList(utf8.encode(plainText)),
+          utf8.encode(plainText),
           iv: IV.fromBase64(base64IV),
         )
         .base64;
@@ -24,12 +24,10 @@ class CryptoUtil {
   static String decryptFromBase64(
       String base64CipherText, String base64Key, String base64IV) {
     final encrypter = AES(Key.fromBase64(base64Key));
-    return utf8.decode(encrypter
-        .decrypt(
-          Encrypted.fromBase64(base64CipherText),
-          iv: IV.fromBase64(base64IV),
-        )
-        .toList());
+    return utf8.decode(encrypter.decrypt(
+      Encrypted.fromBase64(base64CipherText),
+      iv: IV.fromBase64(base64IV),
+    ));
   }
 
   static Future<String> encryptFileToFile(