Selaa lähdekoodia

Download and save encrypted files

Vishnu Mohandas 5 vuotta sitten
vanhempi
commit
746fb18cf7
4 muutettua tiedostoa jossa 130 lisäystä ja 45 poistoa
  1. 3 1
      lib/core/configuration.dart
  2. 14 0
      lib/models/file.dart
  3. 75 25
      lib/photo_sync_manager.dart
  4. 38 19
      lib/utils/crypto_util.dart

+ 3 - 1
lib/core/configuration.dart

@@ -79,7 +79,9 @@ class Configuration {
     await _preferences.setString(keyKey, key);
   }
 
-  String getKey(String passphrase) {
+  // TODO: Encrypt with a passphrase and store in secure storage
+  String getKey() {
+    // return "hello";
     return _preferences.getString(keyKey);
   }
 

+ 14 - 0
lib/models/file.dart

@@ -8,6 +8,7 @@ import 'package:path/path.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/location.dart';
+import 'package:photos/utils/crypto_util.dart';
 
 class File {
   int generatedID;
@@ -24,6 +25,7 @@ class File {
   FileType fileType;
 
   File();
+
   File.fromJson(Map<String, dynamic> json) {
     uploadedFileID = json["id"];
     ownerID = json["ownerID"];
@@ -97,6 +99,18 @@ class File {
     }
   }
 
+  void applyMetadata(Map<String, dynamic> metadata) {
+    localID = metadata["localID"];
+    title = metadata["title"];
+    deviceFolder = metadata["deviceFolder"];
+    creationTime = metadata["creationTime"];
+    modificationTime = metadata["modificationTime"];
+    final latitude = metadata["latitude"];
+    final longitude = metadata["longitude"];
+    location = Location(latitude, longitude);
+    fileType = getFileType(metadata["fileType"]);
+  }
+
   Map<String, dynamic> getMetadata() {
     final metadata = Map<String, dynamic>();
     metadata["localID"] = localID;

+ 75 - 25
lib/photo_sync_manager.dart

@@ -31,8 +31,9 @@ class PhotoSyncManager {
   SharedPreferences _prefs;
   String _encryptedFilesDirectory;
 
-  static final _lastSyncTimeKey = "last_sync_time";
-  static final _lastDBUpdationTimeKey = "last_db_updation_time";
+  static final _syncTimeKey = "sync_time";
+  static final _encryptedFilesSyncTimeKey = "encrypted_files_sync_time";
+  static final _dbUpdationTimeKey = "db_updation_time";
   static final _diffLimit = 100;
 
   PhotoSyncManager._privateConstructor() {
@@ -68,7 +69,7 @@ class PhotoSyncManager {
   }
 
   bool hasScannedDisk() {
-    return _prefs.containsKey(_lastDBUpdationTimeKey);
+    return _prefs.containsKey(_dbUpdationTimeKey);
   }
 
   Future<void> _doSync() async {
@@ -77,7 +78,7 @@ class PhotoSyncManager {
       _logger.severe("Did not get permission");
     }
     final syncStartTime = DateTime.now().microsecondsSinceEpoch;
-    var lastDBUpdationTime = _prefs.getInt(_lastDBUpdationTimeKey);
+    var lastDBUpdationTime = _prefs.getInt(_dbUpdationTimeKey);
     if (lastDBUpdationTime == null) {
       lastDBUpdationTime = 0;
     }
@@ -153,14 +154,15 @@ class PhotoSyncManager {
       return Future.error("Account not configured yet");
     }
     await _downloadDiff();
+    await _downloadEncryptedFilesDiff();
     await _uploadDiff();
     await _deletePhotosOnServer();
   }
 
   Future<void> _downloadDiff() async {
-    final diff = await _getDiff(_getLastSyncTimestamp(), _diffLimit);
+    final diff = await _getDiff(_getSyncTime(), _diffLimit);
     if (diff != null && diff.isNotEmpty) {
-      await _storeDiff(diff);
+      await _storeDiff(diff, _syncTimeKey);
       FileRepository.instance.reloadFiles();
       if (diff.length == _diffLimit) {
         return await _downloadDiff();
@@ -168,12 +170,32 @@ class PhotoSyncManager {
     }
   }
 
-  int _getLastSyncTimestamp() {
-    var lastSyncTimestamp = _prefs.getInt(_lastSyncTimeKey);
-    if (lastSyncTimestamp == null) {
-      lastSyncTimestamp = 0;
+  int _getSyncTime() {
+    var syncTime = _prefs.getInt(_syncTimeKey);
+    if (syncTime == null) {
+      syncTime = 0;
     }
-    return lastSyncTimestamp;
+    return syncTime;
+  }
+
+  Future<void> _downloadEncryptedFilesDiff() async {
+    final diff =
+        await _getEncryptedFilesDiff(_getEncryptedFilesSyncTime(), _diffLimit);
+    if (diff != null && diff.isNotEmpty) {
+      await _storeDiff(diff, _encryptedFilesSyncTimeKey);
+      FileRepository.instance.reloadFiles();
+      if (diff.length == _diffLimit) {
+        return await _downloadEncryptedFilesDiff();
+      }
+    }
+  }
+
+  int _getEncryptedFilesSyncTime() {
+    var syncTime = _prefs.getInt(_encryptedFilesSyncTimeKey);
+    if (syncTime == null) {
+      syncTime = 0;
+    }
+    return syncTime;
   }
 
   Future<void> _uploadDiff() async {
@@ -193,7 +215,7 @@ class PhotoSyncManager {
         }
         await _db.update(file.generatedID, uploadedFile.uploadedFileID,
             uploadedFile.updationTime);
-        _prefs.setInt(_lastSyncTimeKey, uploadedFile.updationTime);
+        _prefs.setInt(_syncTimeKey, uploadedFile.updationTime);
 
         Bus.instance.fire(PhotoUploadEvent(
             completed: i + 1, total: photosToBeUploaded.length));
@@ -204,7 +226,7 @@ class PhotoSyncManager {
     }
   }
 
-  Future _storeDiff(List<File> diff) async {
+  Future _storeDiff(List<File> diff, String prefKey) async {
     for (File file in diff) {
       try {
         final existingPhoto = await _db.getMatchingFile(
@@ -220,7 +242,7 @@ class PhotoSyncManager {
         file.localID = null; // File uploaded from a different device
         await _db.insert(file);
       }
-      await _prefs.setInt(_lastSyncTimeKey, file.updationTime);
+      await _prefs.setInt(prefKey, file.updationTime);
     }
   }
 
@@ -245,14 +267,43 @@ class PhotoSyncManager {
     }
   }
 
+  Future<List<File>> _getEncryptedFilesDiff(int lastSyncTime, int limit) async {
+    Response response = await _dio.get(
+      Configuration.instance.getHttpEndpoint() + "/encrypted-files/diff",
+      options:
+          Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
+      queryParameters: {
+        "sinceTimestamp": lastSyncTime,
+        "limit": limit,
+      },
+    ).catchError((e) => _logger.severe(e));
+    if (response != null) {
+      Bus.instance.fire(RemoteSyncEvent(true));
+      return (response.data["diff"] as List).map((json) {
+        final file = File();
+        file.uploadedFileID = json["id"];
+        file.ownerID = json["ownerID"];
+        file.updationTime = json["updationTime"];
+        Map<String, dynamic> metadata = jsonDecode(CryptoUtil.decryptFromBase64(
+            json["metadata"],
+            Configuration.instance.getKey(),
+            json["metadataIV"]));
+        file.applyMetadata(metadata);
+        return file;
+      }).toList();
+    } else {
+      Bus.instance.fire(RemoteSyncEvent(false));
+      return null;
+    }
+  }
+
   Future<File> _uploadEncryptedFile(File file) async {
-    final key = Configuration.instance.getKey("hello");
+    final key = Configuration.instance.getKey();
 
     final filePath = (await (await file.getAsset()).originFile).path;
     final encryptedFileName = file.generatedID.toString() + ".aes";
     final encryptedFilePath = _encryptedFilesDirectory + encryptedFileName;
-    final fileIV = CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
-    await CryptoUtil.encryptFile(filePath, encryptedFilePath, key, fileIV);
+    await CryptoUtil.encryptFileToFile(filePath, encryptedFilePath, key);
 
     final thumbnailData = (await (await file.getAsset())
         .thumbDataWithSize(THUMBNAIL_LARGE_SIZE, THUMBNAIL_LARGE_SIZE));
@@ -260,25 +311,23 @@ class PhotoSyncManager {
         file.generatedID.toString() + "_thumbnail.aes";
     final encryptedThumbnailPath =
         _encryptedFilesDirectory + encryptedThumbnailName;
-    final thumbnailIV =
-        CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
-    await CryptoUtil.encryptData(
-        thumbnailData, encryptedThumbnailPath, key, thumbnailIV);
+    await CryptoUtil.encryptDataToFile(
+        thumbnailData, encryptedThumbnailPath, key);
 
     final metadata = jsonEncode(file.getMetadata());
     final metadataIV =
         CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
-    final encryptedMetadata = CryptoUtil.encrypt(metadata, key, metadataIV);
+    final encryptedMetadata =
+        CryptoUtil.encryptToBase64(metadata, key, metadataIV);
     final formData = FormData.fromMap({
       "file": MultipartFile.fromFileSync(encryptedFilePath,
           filename: encryptedFileName),
-      "fileIV": fileIV,
       "thumbnail": MultipartFile.fromFileSync(encryptedThumbnailPath,
           filename: encryptedThumbnailName),
-      "thumbnailIV": thumbnailIV,
       "metadata": encryptedMetadata,
       "metadataIV": metadataIV,
     });
+
     return _dio
         .post(
       Configuration.instance.getHttpEndpoint() + "/encrypted-files",
@@ -288,6 +337,7 @@ class PhotoSyncManager {
     )
         .then((response) {
       io.File(encryptedFilePath).deleteSync();
+      io.File(encryptedThumbnailPath).deleteSync();
       final data = response.data;
       file.uploadedFileID = data["id"];
       file.updationTime = data["updationTime"];
@@ -350,6 +400,6 @@ class PhotoSyncManager {
   Future<bool> _insertFilesToDB(List<File> files, int timestamp) async {
     await _db.insertMultiple(files);
     _logger.info("Inserted " + files.length.toString() + " files.");
-    return await _prefs.setInt(_lastDBUpdationTimeKey, timestamp);
+    return await _prefs.setInt(_dbUpdationTimeKey, timestamp);
   }
 }

+ 38 - 19
lib/utils/crypto_util.dart

@@ -2,38 +2,57 @@ import 'dart:typed_data';
 
 import 'package:aes_crypt/aes_crypt.dart';
 import 'package:encrypt/encrypt.dart';
+import 'dart:convert';
 
 class CryptoUtil {
   static String getBase64EncodedSecureRandomString({int length = 32}) {
     return SecureRandom(length).base64;
   }
 
-  static String encrypt(String plainText, String base64Key, String base64IV) {
-    final encrypter = Encrypter(AES(Key.fromBase64(base64Key)));
-    final iv = base64IV == null ? null : IV.fromBase64(base64IV);
-    return encrypter.encrypt(plainText, iv: iv).base64;
+  static String encryptToBase64(
+      String plainText, String base64Key, String base64IV) {
+    final encrypter = AES(Key.fromBase64(base64Key));
+    return encrypter
+        .encrypt(
+          Uint8List.fromList(utf8.encode(plainText)),
+          iv: IV.fromBase64(base64IV),
+        )
+        .base64;
   }
 
-  static String decrypt(String cipherText, String base64Key) {
-    final encrypter = Encrypter(AES(Key.fromBase64(base64Key)));
-    return encrypter.decrypt(Encrypted.fromBase64(cipherText));
+  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());
   }
 
-  static Future<void> encryptFile(String sourcePath, String destinationPath,
-      String base64Key, String base64IV) async {
-    final encrypter = AesCrypt("hello");
-    encrypter.aesSetParams(Key.fromBase64(base64Key).bytes,
-        IV.fromBase64(base64IV).bytes, AesMode.cbc);
-    encrypter.setOverwriteMode(AesCryptOwMode.on);
+  static Future<void> encryptFileToFile(
+      String sourcePath, String destinationPath, String key) async {
+    final encrypter = _getEncrypter(key);
     await encrypter.encryptFile(sourcePath, destinationPath);
   }
 
-  static Future<void> encryptData(Uint8List source, String destinationPath,
-      String base64Key, String base64IV) async {
-    final encrypter = AesCrypt("hello");
-    encrypter.aesSetParams(Key.fromBase64(base64Key).bytes,
-        IV.fromBase64(base64IV).bytes, AesMode.cbc);
-    encrypter.setOverwriteMode(AesCryptOwMode.on);
+  static Future<void> encryptDataToFile(
+      Uint8List source, String destinationPath, String key) async {
+    final encrypter = _getEncrypter(key);
     await encrypter.encryptDataToFile(source, destinationPath);
   }
+
+  static Future<void> decryptFileToFile(
+      String sourcePath, String destinationPath, String key) async {
+    final encrypter = _getEncrypter(key);
+    await encrypter.decryptFile(sourcePath, destinationPath);
+  }
+
+  static AesCrypt _getEncrypter(String key) {
+    final encrypter = AesCrypt(key);
+    encrypter.aesSetMode(AesMode.cbc);
+    encrypter.setOverwriteMode(AesCryptOwMode.on);
+    return encrypter;
+  }
 }