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 {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 121;
const SYNC_VERSION = 3;
const APP_DB_VERSION = 122;
const SYNC_VERSION = 4;
module.exports = {
appVersion: packageJson.version,

View file

@ -18,25 +18,29 @@ function shaArray(content) {
}
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) {
padded = padded.slice(0, 16);
data = Buffer.concat([data, Buffer.from(zeros)]);
}
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) {
throw new Error("No data key!");
}
const plainTextBuffer = Buffer.from(plainText);
const iv = crypto.randomBytes(ivLength);
const cipher = crypto.createCipheriv('aes-128-cbc', pad(key), pad(iv));
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()]);
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) {
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 cipherTextBuffer = Buffer.from(cipherText, 'base64');
const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]);
const digest = decryptedBytes.slice(0, 4);
@ -70,8 +80,8 @@ function decrypt(key, iv, cipherText) {
return payload;
}
function decryptString(dataKey, iv, cipherText) {
const buffer = decrypt(dataKey, iv, cipherText);
function decryptString(dataKey, cipherText) {
const buffer = decrypt(dataKey, cipherText);
const str = buffer.toString('utf-8');
@ -84,26 +94,8 @@ function decryptString(dataKey, iv, cipherText) {
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 = {
encrypt,
decrypt,
decryptString,
noteTitleIv,
noteContentIv
decryptString
};

View file

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

View file

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

View file

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