package com.example.passrepo.io; import android.app.AlertDialog; import android.content.Context; import android.widget.Toast; import com.example.passrepo.Consts; import com.example.passrepo.DecryptionFailedException; import com.example.passrepo.PassRepoBaseSecurityException; import com.example.passrepo.crypto.Encryption; import com.example.passrepo.crypto.Encryption.CipherText; import com.example.passrepo.crypto.PasswordHasher; import com.example.passrepo.dummy.DummyContent; import com.example.passrepo.model.Model; import com.example.passrepo.util.GsonHelper; import com.example.passrepo.util.Logger; import com.google.common.base.Charsets; import com.google.common.base.Stopwatch; import com.google.common.io.CharStreams; import com.google.common.io.InputSupplier; import com.google.common.io.OutputSupplier; import java.io.*; import java.util.concurrent.TimeUnit; public class IO { private final static boolean DEBUG_DO_NOT_ENCRYPT = false; public static String modelToEncryptedString(Model model) { if (DEBUG_DO_NOT_ENCRYPT) { return GsonHelper.customGson.toJson(model); } byte[] plainText = GsonHelper.customGson.toJson(model).getBytes(Charsets.UTF_8); CipherText cipherText = Encryption.encrypt(plainText, model.keys); EncryptedFile encryptedFile = new EncryptedFile(model.scryptParameters, cipherText); return GsonHelper.customGson.toJson(encryptedFile); } public static Model modelFromEncryptedString(String encryptedString, PasswordHasher.Keys keys) throws PassRepoBaseSecurityException { if (DEBUG_DO_NOT_ENCRYPT) { Model result = GsonHelper.customGson.fromJson(encryptedString, Model.class); result.populateIdsToPasswordEntriesMap(); return result; } EncryptedFile encryptedFile = GsonHelper.customGson.fromJson(encryptedString, EncryptedFile.class); if (!encryptedFile.cipherText.hmacVerified(keys.hmacKey)) { throw new DecryptionFailedException("MAC doesn't match"); } String modelJson = new String(Encryption.decrypt(encryptedFile.cipherText, keys.encryptionKey), Charsets.UTF_8); Model result = GsonHelper.customGson.fromJson(modelJson, Model.class); result.populateIdsToPasswordEntriesMap(); // TODO: For some reason isn't called by the GsonHelper. Temporary workaround.. result.keys = keys; result.scryptParameters = encryptedFile.scryptParameters; return result; } public static void loadModelFromDisk(final Context context) { try { Logger.i("IO", "Loading model from disk"); String fileContents = CharStreams.toString(new InputSupplier<InputStreamReader>() { public InputStreamReader getInput() throws IOException { return new InputStreamReader(context.openFileInput(Consts.PASS_REPO_LOCAL_DATABASE_FILENAME), Charsets.UTF_8); } }); Model.currentModel = IO.modelFromEncryptedString(fileContents, DummyContent.dummyKeys); Logger.i("IO", "Model loaded. Results are: " + fileContents); } catch (PassRepoBaseSecurityException e) { // TODO: proper UI flow for failed decryption instead of crashing throw new RuntimeException(e); } catch (FileNotFoundException e) { // Model doesn't exist on disk (probably first time install), use the dummy instead. Model.currentModel = DummyContent.model; Logger.i("IO", "Loaded model from dummy content (first time install)"); } catch (IOException e) { throw new RuntimeException(e); } } public static void saveModelToDisk(final Context context) { GlobalExecutors.BACKGROUND_PARALLEL_EXECUTOR.execute(new Runnable() { public void run() { try { final Stopwatch stopwatch = new Stopwatch().start(); String fileContents = modelToEncryptedString(Model.currentModel); final long elapsedMs = stopwatch.elapsed(TimeUnit.MILLISECONDS); // Using OutputSupplier<OutputStream> fails to compile for some reason, using OutputStreamWriter instead CharStreams.write(fileContents, new OutputSupplier<OutputStreamWriter>() { public OutputStreamWriter getOutput() throws IOException { return new OutputStreamWriter(context.openFileOutput(Consts.PASS_REPO_LOCAL_DATABASE_FILENAME, Context.MODE_PRIVATE)); } }); GlobalExecutors.MAIN_THREAD_EXECUTOR.execute(new Runnable() { public void run() { //Toast.makeText(context, "Saved, encryption time=" + elapsedMs + "ms", Toast.LENGTH_LONG).show(); } }); } catch (IOException e) { new AlertDialog.Builder(context).setTitle("Error writing file").setMessage(e.getMessage()).setCancelable(true).show(); } } }); } }