mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
store iv directly in the respective columns
This commit is contained in:
parent
dffdb82288
commit
cc27f16088
62
db/migrations/0122__add_iv_to_columns.js
Normal file
62
db/migrations/0122__add_iv_to_columns.js
Normal 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();
|
||||
};
|
@ -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,
|
||||
|
@ -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
|
||||
};
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user