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 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,
|
||||||
|
@ -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
|
|
||||||
};
|
};
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user