mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +01:00
feat: use libsodium for all cryptography
This commit is contained in:
@@ -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
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user