require("app-module-path").addPath(__dirname); const { time } = require("lib/time-utils.js"); const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, } = require("test-utils.js"); const Folder = require("lib/models/Folder.js"); const Note = require("lib/models/Note.js"); const Tag = require("lib/models/Tag.js"); const { Database } = require("lib/database.js"); const Setting = require("lib/models/Setting.js"); const BaseItem = require("lib/models/BaseItem.js"); const BaseModel = require("lib/BaseModel.js"); const MasterKey = require("lib/models/MasterKey"); const SyncTargetRegistry = require("lib/SyncTargetRegistry.js"); const EncryptionService = require("lib/services/EncryptionService.js"); process.on("unhandledRejection", (reason, p) => { console.log("Unhandled Rejection at: Promise", p, "reason:", reason); }); jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built let service = null; describe("Encryption", function() { beforeEach(async done => { await setupDatabaseAndSynchronizer(1); //await setupDatabaseAndSynchronizer(2); //await switchClient(1); service = new EncryptionService(); BaseItem.encryptionService_ = service; Setting.setValue("encryption.enabled", true); done(); }); it("should encode and decode header", async done => { const header = { encryptionMethod: EncryptionService.METHOD_SJCL, masterKeyId: "01234568abcdefgh01234568abcdefgh", }; const encodedHeader = service.encodeHeader_(header); const decodedHeader = service.decodeHeader_(encodedHeader); delete decodedHeader.length; expect(objectsEqual(header, decodedHeader)).toBe(true); done(); }); it("should generate and decrypt a master key", async done => { const masterKey = await service.generateMasterKey("123456"); expect(!!masterKey.checksum).toBe(true); expect(!!masterKey.content).toBe(true); let hasThrown = false; try { await service.decryptMasterKey(masterKey, "wrongpassword"); } catch (error) { hasThrown = true; } expect(hasThrown).toBe(true); const decryptedMasterKey = await service.decryptMasterKey(masterKey, "123456"); expect(decryptedMasterKey.length).toBe(512); done(); }); it("should encrypt and decrypt with a master key", async done => { let masterKey = await service.generateMasterKey("123456"); masterKey = await MasterKey.save(masterKey); await service.loadMasterKey(masterKey, "123456", true); const cipherText = await service.encryptString("some secret"); const plainText = await service.decryptString(cipherText); expect(plainText).toBe("some secret"); // Test that a long string, that is going to be split into multiple chunks, encrypt // and decrypt properly too. let veryLongSecret = ""; for (let i = 0; i < service.chunkSize() * 3; i++) veryLongSecret += Math.floor(Math.random() * 9); const cipherText2 = await service.encryptString(veryLongSecret); const plainText2 = await service.decryptString(cipherText2); expect(plainText2 === veryLongSecret).toBe(true); done(); }); it("should fail to decrypt if master key not present", async done => { let masterKey = await service.generateMasterKey("123456"); masterKey = await MasterKey.save(masterKey); await service.loadMasterKey(masterKey, "123456", true); const cipherText = await service.encryptString("some secret"); await service.unloadMasterKey(masterKey); let hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); expect(hasThrown).toBe(true); done(); }); it("should fail to decrypt if data tampered with", async done => { let masterKey = await service.generateMasterKey("123456"); masterKey = await MasterKey.save(masterKey); await service.loadMasterKey(masterKey, "123456", true); let cipherText = await service.encryptString("some secret"); cipherText += "ABCDEFGHIJ"; let hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); expect(hasThrown).toBe(true); done(); }); it("should encrypt and decrypt notes and folders", async done => { let masterKey = await service.generateMasterKey("123456"); masterKey = await MasterKey.save(masterKey); await service.loadMasterKey(masterKey, "123456", true); let folder = await Folder.save({ title: "folder" }); let note = await Note.save({ title: "encrypted note", body: "something", parent_id: folder.id }); let serialized = await Note.serializeForSync(note); let deserialized = Note.filter(await Note.unserialize(serialized)); // Check that required properties are not encrypted expect(deserialized.id).toBe(note.id); expect(deserialized.parent_id).toBe(note.parent_id); expect(deserialized.updated_time).toBe(note.updated_time); // Check that at least title and body are encrypted expect(!deserialized.title).toBe(true); expect(!deserialized.body).toBe(true); // Check that encrypted data is there expect(!!deserialized.encryption_cipher_text).toBe(true); encryptedNote = await Note.save(deserialized); decryptedNote = await Note.decrypt(encryptedNote); expect(decryptedNote.title).toBe(note.title); expect(decryptedNote.body).toBe(note.body); expect(decryptedNote.id).toBe(note.id); expect(decryptedNote.parent_id).toBe(note.parent_id); done(); }); it("should encrypt and decrypt files", async done => { let masterKey = await service.generateMasterKey("123456"); masterKey = await MasterKey.save(masterKey); await service.loadMasterKey(masterKey, "123456", true); const sourcePath = __dirname + "/../tests/support/photo.jpg"; const encryptedPath = __dirname + "/data/photo.crypted"; const decryptedPath = __dirname + "/data/photo.jpg"; await service.encryptFile(sourcePath, encryptedPath); await service.decryptFile(encryptedPath, decryptedPath); expect(fileContentEqual(sourcePath, encryptedPath)).toBe(false); expect(fileContentEqual(sourcePath, decryptedPath)).toBe(true); done(); }); });