feat: use libsodium for all cryptography

This commit is contained in:
thecodrr
2020-04-11 17:20:37 +05:00
parent 7dae60e4f0
commit 668fe6fd33
7 changed files with 30 additions and 53 deletions

View File

@@ -7,7 +7,7 @@ async function read(key) {
async function readMulti(keys) { async function readMulti(keys) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const result = []; const result = [];
keys.forEach(key => { keys.forEach((key) => {
result.push([key, storage[key]]); result.push([key, storage[key]]);
}); });
resolve(result); resolve(result);
@@ -24,20 +24,10 @@ function clear() {
storage = {}; storage = {};
} }
function encrypt(password, data) {
return new Promise((resolve, reject) =>
resolve({ iv: "some iv", cipher: data })
);
}
function decrypt(password, data) {
return new Promise((resolve, reject) => resolve(data.cipher));
}
module.exports = { module.exports = {
read, read,
readMulti, readMulti,
write, write,
remove, remove,
clear, clear,
encrypt,
decrypt
}; };

View File

@@ -5,7 +5,7 @@ beforeEach(async () => {
}); });
test("create vault", () => test("create vault", () =>
databaseTest().then(async db => { databaseTest().then(async (db) => {
expect(await db.vault.create("password")).toBe(true); expect(await db.vault.create("password")).toBe(true);
const lockKey = await db.context.read("lockKey"); const lockKey = await db.context.read("lockKey");
expect(lockKey).toBeDefined(); expect(lockKey).toBeDefined();
@@ -14,29 +14,29 @@ test("create vault", () =>
})); }));
test("unlock vault", () => test("unlock vault", () =>
databaseTest().then(async db => { databaseTest().then(async (db) => {
expect(await db.vault.create("password")).toBe(true); expect(await db.vault.create("password")).toBe(true);
expect(await db.vault.unlock("password")).toBe(true); expect(await db.vault.unlock("password")).toBe(true);
})); }));
test("unlock non-existent vault", () => test("unlock non-existent vault", () =>
databaseTest().then(async db => { databaseTest().then(async (db) => {
db.vault db.vault
.unlock("password") .unlock("password")
.catch(err => expect(err.message).toBe("ERR_NO_VAULT")); .catch((err) => expect(err.message).toBe("ERR_NO_VAULT"));
})); }));
test("unlock vault with wrong password", () => test("unlock vault with wrong password", () =>
databaseTest().then(async db => { databaseTest().then(async (db) => {
await db.vault.create("password"); await db.vault.create("password");
db.vault return db.vault
.unlock("passwrd") .unlock("passwrd")
.catch(err => expect(err.message).toBe("ERR_WRNG_PWD")); .catch((err) => expect(err.message).toBe("ERR_WRONG_PASSWORD"));
})); }));
test("lock a note when no vault has been created", () => test("lock a note when no vault has been created", () =>
noteTest().then(({ db, id }) => { noteTest().then(({ db, id }) => {
db.vault.add(id).catch(err => { return db.vault.add(id).catch((err) => {
expect(err.message).toBe("ERR_NO_VAULT"); expect(err.message).toBe("ERR_NO_VAULT");
}); });
})); }));

View File

@@ -8,6 +8,7 @@ import Vault from "./vault";
import Lookup from "./lookup"; import Lookup from "./lookup";
import Content from "../collections/content"; import Content from "../collections/content";
import Conflicts from "./conflicts"; import Conflicts from "./conflicts";
import Crypto from "../utils/crypto";
class Database { class Database {
constructor(context) { constructor(context) {
@@ -40,6 +41,8 @@ class Database {
this.vault = new Vault(this, this.context); this.vault = new Vault(this, this.context);
this.conflicts = new Conflicts(this); this.conflicts = new Conflicts(this);
this.lookup = new Lookup(this); this.lookup = new Lookup(this);
this.crypto = new Crypto();
await this.crypto.init();
} }
sync() { sync() {

View File

@@ -13,14 +13,14 @@ export default class Vault {
this.ERRORS = { this.ERRORS = {
noVault: "ERR_NO_VAULT", noVault: "ERR_NO_VAULT",
vaultLocked: "ERR_VAULT_LOCKED", vaultLocked: "ERR_VAULT_LOCKED",
wrongPassword: "ERR_WRONG_PASSWORD" wrongPassword: "ERR_WRONG_PASSWORD",
}; };
} }
async create(password) { async create(password) {
const lockKey = await this._context.read("lockKey"); const lockKey = await this._context.read("lockKey");
if (!lockKey || !lockKey.cipher || !lockKey.iv) { if (!lockKey || !lockKey.cipher || !lockKey.iv) {
const encryptedData = await this._context.encrypt(password, this._key); const encryptedData = await this._db.crypto.encrypt(password, this._key);
await this._context.write("lockKey", encryptedData); await this._context.write("lockKey", encryptedData);
this._password = password; this._password = password;
} }
@@ -32,7 +32,7 @@ export default class Vault {
if (!(await this._exists(lockKey))) throw new Error("ERR_NO_VAULT"); if (!(await this._exists(lockKey))) throw new Error("ERR_NO_VAULT");
var data; var data;
try { try {
data = await this._context.decrypt(password, lockKey); data = this._db.crypto.decrypt(password, lockKey);
} catch (e) { } catch (e) {
throw new Error(this.ERRORS.wrongPassword); throw new Error(this.ERRORS.wrongPassword);
} }
@@ -96,8 +96,8 @@ export default class Vault {
if (!delta.ops) delta = await this._db.delta.get(deltaId); if (!delta.ops) delta = await this._db.delta.get(deltaId);
if (text === textId) text = await this._db.text.get(textId); if (text === textId) text = await this._db.text.get(textId);
text = await this._context.encrypt(this._password, text); text = this._db.crypto.encrypt(this._password, text);
delta = await this._context.encrypt(this._password, delta); delta = this._db.crypto.encrypt(this._password, delta);
await this._db.text.add({ id: textId, data: text }); await this._db.text.add({ id: textId, data: text });
await this._db.delta.add({ id: deltaId, data: delta }); await this._db.delta.add({ id: deltaId, data: delta });
@@ -107,14 +107,14 @@ export default class Vault {
let { text, delta } = { ...content }; let { text, delta } = { ...content };
text = await this._db.text.get(text); text = await this._db.text.get(text);
text = await this._context.decrypt(this._password, text); text = this._db.crypto.decrypt(this._password, text);
delta = await this._db.text.get(delta); delta = await this._db.text.get(delta);
delta = await this._context.decrypt(this._password, delta); delta = JSON.parse(this._db.crypto.decrypt(this._password, delta));
return { return {
delta, delta,
text text,
}; };
} }
@@ -135,7 +135,7 @@ export default class Vault {
return await this._db.notes.add({ return await this._db.notes.add({
id, id,
locked: true locked: true,
}); });
} }
@@ -147,7 +147,7 @@ export default class Vault {
if (perm) { if (perm) {
await this._db.notes.add({ await this._db.notes.add({
id: note.id, id: note.id,
locked: false locked: false,
}); });
await this._db.delta.add({ id: note.content.delta, data: delta }); await this._db.delta.add({ id: note.content.delta, data: delta });
await this._db.text.add({ id: note.content.text, data: text }); await this._db.text.add({ id: note.content.text, data: text });
@@ -156,7 +156,7 @@ export default class Vault {
return { return {
...note, ...note,
content: { delta } content: { delta },
}; };
} }
} }

View File

@@ -17,10 +17,4 @@ export default class Storage {
remove(key) { remove(key) {
return this.storage.remove(key); return this.storage.remove(key);
} }
encrypt(password, data) {
return this.storage.encrypt(password, data);
}
decrypt(password, cipher) {
return this.storage.decrypt(password, cipher);
}
} }

View File

@@ -8,18 +8,13 @@ test("libsodium should load", async () => {
test("crypto should throw if init has not been called", () => { test("crypto should throw if init has not been called", () => {
const crypto = new Crypto(); const crypto = new Crypto();
expect(() => expect(() => crypto.encrypt("i_am_a_password", "hello world")).toThrow();
crypto.encrypt({ password: "i_am_a_password", data: "hello world" })
).toThrow();
}); });
test("encrypt should encrypt the data", async () => { test("encrypt should encrypt the data", async () => {
const crypto = new Crypto(); const crypto = new Crypto();
await crypto.init(); await crypto.init();
const result = crypto.encrypt({ const result = crypto.encrypt("i_am_a_password", "hello world");
password: "i_am_a_password",
data: "hello world",
});
expect(result.cipher).not.toBe("hello world"); expect(result.cipher).not.toBe("hello world");
expect(result.iv).toBeDefined(); expect(result.iv).toBeDefined();
expect(result.salt).toBeDefined(); expect(result.salt).toBeDefined();
@@ -28,14 +23,8 @@ test("encrypt should encrypt the data", async () => {
test("decrypt should result in plain text", async () => { test("decrypt should result in plain text", async () => {
const crypto = new Crypto(); const crypto = new Crypto();
await crypto.init(); await crypto.init();
const result = crypto.encrypt({ const result = crypto.encrypt("i_am_a_password", "hello world");
password: "i_am_a_password",
data: "hello world",
});
const decrypted = crypto.decrypt({ const decrypted = crypto.decrypt("i_am_a_password", { ...result });
password: "i_am_a_password",
data: { ...result },
});
expect(decrypted).toBe("hello world"); expect(decrypted).toBe("hello world");
}); });

View File

@@ -38,7 +38,8 @@ class Crypto {
return { key, salt: saltHex }; return { key, salt: saltHex };
} }
encrypt({ password, data }) { encrypt(password, data) {
if (typeof data === "object") data = JSON.stringify(data);
this._throwIfNotReady(); this._throwIfNotReady();
const { key, salt } = this._deriveKey( const { key, salt } = this._deriveKey(
password, password,
@@ -65,7 +66,7 @@ class Crypto {
}; };
} }
decrypt({ password, data: { salt, iv, cipher } }) { decrypt(password, { salt, iv, cipher }) {
this._throwIfNotReady(); this._throwIfNotReady();
const { key } = this._deriveKey( const { key } = this._deriveKey(
password, password,