store iv directly in the respective columns

This commit is contained in:
azivner 2019-01-11 23:04:51 +01:00
parent dffdb82288
commit cc27f16088
6 changed files with 99 additions and 57 deletions

View File

@ -0,0 +1,62 @@
const sql = require('../../src/services/sql');
function prependIv(cipherText, ivText) {
const arr = ivText.split("").map(c => parseInt(c) || 0);
const iv = Buffer.from(arr);
const payload = Buffer.from(cipherText, 'base64');
const complete = Buffer.concat([iv, payload]);
return complete.toString('base64');
}
async function updateEncryptedDataKey() {
const encryptedDataKey = await sql.getValue("SELECT value FROM options WHERE name = 'encryptedDataKey'");
const encryptedDataKeyIv = await sql.getValue("SELECT value FROM options WHERE name = 'encryptedDataKeyIv'");
const newEncryptedDataKey = prependIv(encryptedDataKey, encryptedDataKeyIv);
await sql.execute("UPDATE options SET value = ? WHERE name = 'encryptedDataKey'", [newEncryptedDataKey]);
await sql.execute("DELETE FROM options WHERE name = 'encryptedDataKeyIv'");
await sql.execute("DELETE FROM sync WHERE entityName = 'options' AND entityId = 'encryptedDataKeyIv'");
}
async function updateNotes() {
const protectedNotes = await sql.getRows("SELECT noteId, title, content FROM notes WHERE isProtected = 1");
for (const note of protectedNotes) {
if (note.title !== null) {
note.title = prependIv(note.title, "0" + note.noteId);
}
if (note.content !== null) {
note.content = prependIv(note.content, "1" + note.noteId);
}
await sql.execute("UPDATE notes SET title = ?, content = ? WHERE noteId = ?", [note.title, note.content, note.noteId]);
}
}
async function updateNoteRevisions() {
const protectedNoteRevisions = await sql.getRows("SELECT noteRevisionId, title, content FROM note_revisions WHERE isProtected = 1");
for (const noteRevision of protectedNoteRevisions) {
if (noteRevision.title !== null) {
noteRevision.title = prependIv(noteRevision.title, "0" + noteRevision.noteRevisionId);
}
if (noteRevision.content !== null) {
noteRevision.content = prependIv(noteRevision.content, "1" + noteRevision.noteRevisionId);
}
await sql.execute("UPDATE note_revisions SET title = ?, content = ? WHERE noteRevisionId = ?", [noteRevision.title, noteRevision.content, noteRevision.noteRevisionId]);
}
}
module.exports = async () => {
await updateEncryptedDataKey();
await updateNotes();
await updateNoteRevisions();
};

View File

@ -4,8 +4,8 @@ const build = require('./build');
const packageJson = require('../../package'); const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir'); const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 121; const APP_DB_VERSION = 122;
const SYNC_VERSION = 3; const SYNC_VERSION = 4;
module.exports = { module.exports = {
appVersion: packageJson.version, appVersion: packageJson.version,

View File

@ -18,25 +18,29 @@ function shaArray(content) {
} }
function pad(data) { function pad(data) {
let padded = Array.from(data); if (data.length > 16) {
data = data.slice(0, 16);
}
else if (data.length < 16) {
const zeros = Array(16 - data.length).fill(0);
if (data.length >= 16) { data = Buffer.concat([data, Buffer.from(zeros)]);
padded = padded.slice(0, 16);
} }
else { else {
padded = padded.concat(Array(16 - padded.length).fill(0)); data = Buffer.from(data);
} }
return Buffer.from(padded); return data;
} }
function encrypt(key, iv, plainText) { function encrypt(key, plainText, ivLength = 13) {
if (!key) { if (!key) {
throw new Error("No data key!"); throw new Error("No data key!");
} }
const plainTextBuffer = Buffer.from(plainText); const plainTextBuffer = Buffer.from(plainText);
const iv = crypto.randomBytes(ivLength);
const cipher = crypto.createCipheriv('aes-128-cbc', pad(key), pad(iv)); const cipher = crypto.createCipheriv('aes-128-cbc', pad(key), pad(iv));
const digest = shaArray(plainTextBuffer).slice(0, 4); const digest = shaArray(plainTextBuffer).slice(0, 4);
@ -45,17 +49,23 @@ function encrypt(key, iv, plainText) {
const encryptedData = Buffer.concat([cipher.update(digestWithPayload), cipher.final()]); const encryptedData = Buffer.concat([cipher.update(digestWithPayload), cipher.final()]);
return encryptedData.toString('base64'); const encryptedDataWithIv = Buffer.concat([iv, encryptedData]);
return encryptedDataWithIv.toString('base64');
} }
function decrypt(key, iv, cipherText) { function decrypt(key, cipherText, ivLength = 13) {
if (!key) { if (!key) {
return "[protected]"; return "[protected]";
} }
const cipherTextBufferWithIv = Buffer.from(cipherText, 'base64');
const iv = cipherTextBufferWithIv.slice(0, ivLength);
const cipherTextBuffer = cipherTextBufferWithIv.slice(ivLength);
const decipher = crypto.createDecipheriv('aes-128-cbc', pad(key), pad(iv)); const decipher = crypto.createDecipheriv('aes-128-cbc', pad(key), pad(iv));
const cipherTextBuffer = Buffer.from(cipherText, 'base64');
const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]); const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]);
const digest = decryptedBytes.slice(0, 4); const digest = decryptedBytes.slice(0, 4);
@ -70,8 +80,8 @@ function decrypt(key, iv, cipherText) {
return payload; return payload;
} }
function decryptString(dataKey, iv, cipherText) { function decryptString(dataKey, cipherText) {
const buffer = decrypt(dataKey, iv, cipherText); const buffer = decrypt(dataKey, cipherText);
const str = buffer.toString('utf-8'); const str = buffer.toString('utf-8');
@ -84,26 +94,8 @@ function decryptString(dataKey, iv, cipherText) {
return str; return str;
} }
function noteTitleIv(iv) {
if (!iv) {
throw new Error("Empty iv!");
}
return "0" + iv;
}
function noteContentIv(iv) {
if (!iv) {
throw new Error("Empty iv!");
}
return "1" + iv;
}
module.exports = { module.exports = {
encrypt, encrypt,
decrypt, decrypt,
decryptString, decryptString
noteTitleIv,
noteContentIv
}; };

View File

@ -24,7 +24,6 @@ async function initSyncedOptions(username, password) {
// passwordEncryptionService expects these options to already exist // passwordEncryptionService expects these options to already exist
await optionService.createOption('encryptedDataKey', '', true); await optionService.createOption('encryptedDataKey', '', true);
await optionService.createOption('encryptedDataKeyIv', '', true);
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true); await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
} }

View File

@ -14,13 +14,7 @@ async function verifyPassword(password) {
async function setDataKey(password, plainTextDataKey) { async function setDataKey(password, plainTextDataKey) {
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password); const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
const encryptedDataKeyIv = utils.randomString(16); const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, Buffer.from(plainTextDataKey));
await optionService.setOption('encryptedDataKeyIv', encryptedDataKeyIv);
const buffer = Buffer.from(plainTextDataKey);
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, encryptedDataKeyIv, buffer);
await optionService.setOption('encryptedDataKey', newEncryptedDataKey); await optionService.setOption('encryptedDataKey', newEncryptedDataKey);
} }
@ -28,10 +22,9 @@ async function setDataKey(password, plainTextDataKey) {
async function getDataKey(password) { async function getDataKey(password) {
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password); const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
const encryptedDataKeyIv = await optionService.getOption('encryptedDataKeyIv');
const encryptedDataKey = await optionService.getOption('encryptedDataKey'); const encryptedDataKey = await optionService.getOption('encryptedDataKey');
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKeyIv, encryptedDataKey); const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKey, 16);
return decryptedDataKey; return decryptedDataKey;
} }

View File

@ -38,9 +38,7 @@ function decryptNoteTitle(noteId, encryptedTitle) {
const dataKey = getDataKey(); const dataKey = getDataKey();
try { try {
const iv = dataEncryptionService.noteTitleIv(noteId); return dataEncryptionService.decryptString(dataKey, encryptedTitle);
return dataEncryptionService.decryptString(dataKey, iv, encryptedTitle);
} }
catch (e) { catch (e) {
e.message = `Cannot decrypt note title for noteId=${noteId}: ` + e.message; e.message = `Cannot decrypt note title for noteId=${noteId}: ` + e.message;
@ -57,17 +55,15 @@ function decryptNote(note) {
try { try {
if (note.title) { if (note.title) {
note.title = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteTitleIv(note.noteId), note.title); note.title = dataEncryptionService.decryptString(dataKey, note.title);
} }
if (note.content) { if (note.content) {
const contentIv = dataEncryptionService.noteContentIv(note.noteId); if (note.type === 'file' || note.type === 'image') {
note.content = dataEncryptionService.decrypt(dataKey, note.content);
if (note.type === 'file') {
note.content = dataEncryptionService.decrypt(dataKey, contentIv, note.content);
} }
else { else {
note.content = dataEncryptionService.decryptString(dataKey, contentIv, note.content); note.content = dataEncryptionService.decryptString(dataKey, note.content);
} }
} }
} }
@ -91,26 +87,26 @@ function decryptNoteRevision(hist) {
} }
if (hist.title) { if (hist.title) {
hist.title = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteTitleIv(hist.noteRevisionId), hist.title); hist.title = dataEncryptionService.decryptString(dataKey, hist.title);
} }
if (hist.content) { if (hist.content) {
hist.content = dataEncryptionService.decryptString(dataKey, dataEncryptionService.noteContentIv(hist.noteRevisionId), hist.content); hist.content = dataEncryptionService.decryptString(dataKey, hist.content);
} }
} }
function encryptNote(note) { function encryptNote(note) {
const dataKey = getDataKey(); const dataKey = getDataKey();
note.title = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteTitleIv(note.noteId), note.title); note.title = dataEncryptionService.encrypt(dataKey, note.title);
note.content = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteContentIv(note.noteId), note.content); note.content = dataEncryptionService.encrypt(dataKey, note.content);
} }
function encryptNoteRevision(revision) { function encryptNoteRevision(revision) {
const dataKey = getDataKey(); const dataKey = getDataKey();
revision.title = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteTitleIv(revision.noteRevisionId), revision.title); revision.title = dataEncryptionService.encrypt(dataKey, revision.title);
revision.content = dataEncryptionService.encrypt(dataKey, dataEncryptionService.noteContentIv(revision.noteRevisionId), revision.content); revision.content = dataEncryptionService.encrypt(dataKey, revision.content);
} }
module.exports = { module.exports = {