mirror of
https://github.com/TriliumNext/Notes.git
synced 2026-04-24 21:59:03 -05:00
server side WIP - saving encrypted note now works, changing terminology of "encrypted note" to "protected note"
This commit is contained in:
@@ -9,7 +9,7 @@ module.exports = {
|
||||
CREATE_NOTE: 'CREATE',
|
||||
DELETE_NOTE: 'DELETE',
|
||||
CHANGE_PARENT: 'PARENT',
|
||||
ENCRYPTION: 'ENCRYPTION',
|
||||
PROTECTED: 'PROTECTED',
|
||||
CHANGE_PASSWORD: 'PASSWORD',
|
||||
SETTINGS: 'SETTINGS',
|
||||
SYNC: 'SYNC'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const protected_session = require('./protected_session');
|
||||
"use strict";
|
||||
|
||||
const utils = require('./utils');
|
||||
const aesjs = require('./aes');
|
||||
const sha256 = require('./sha256');
|
||||
|
||||
function getProtectedSessionId(req) {
|
||||
return req.headers['x-protected-session-id'];
|
||||
@@ -10,6 +12,15 @@ function getDataAes(dataKey) {
|
||||
return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5));
|
||||
}
|
||||
|
||||
function arraysIdentical(a, b) {
|
||||
let i = a.length;
|
||||
if (i !== b.length) return false;
|
||||
while (i--) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function decrypt(dataKey, encryptedBase64) {
|
||||
if (!dataKey) {
|
||||
return "[protected]";
|
||||
@@ -24,10 +35,42 @@ function decrypt(dataKey, encryptedBase64) {
|
||||
const digest = decryptedBytes.slice(0, 4);
|
||||
const payload = decryptedBytes.slice(4);
|
||||
|
||||
const hashArray = sha256Array(payload);
|
||||
|
||||
const computedDigest = hashArray.slice(0, 4);
|
||||
|
||||
if (!arraysIdentical(digest, computedDigest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return aesjs.utils.utf8.fromBytes(payload);
|
||||
}
|
||||
|
||||
function encrypt(dataKey, plainText) {
|
||||
if (!dataKey) {
|
||||
throw new Error("No data key!");
|
||||
}
|
||||
|
||||
const aes = getDataAes(dataKey);
|
||||
|
||||
const payload = Array.from(aesjs.utils.utf8.toBytes(plainText));
|
||||
const digest = sha256Array(payload).slice(0, 4);
|
||||
|
||||
const digestWithPayload = digest.concat(payload);
|
||||
|
||||
const encryptedBytes = aes.encrypt(digestWithPayload);
|
||||
|
||||
return utils.toBase64(encryptedBytes);
|
||||
}
|
||||
|
||||
function sha256Array(content) {
|
||||
const hash = sha256.create();
|
||||
hash.update(content);
|
||||
return hash.array();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getProtectedSessionId,
|
||||
decrypt
|
||||
decrypt,
|
||||
encrypt
|
||||
};
|
||||
@@ -4,7 +4,7 @@ const options = require('./options');
|
||||
const fs = require('fs-extra');
|
||||
const log = require('./log');
|
||||
|
||||
const APP_DB_VERSION = 27;
|
||||
const APP_DB_VERSION = 28;
|
||||
const MIGRATIONS_DIR = "./migrations";
|
||||
|
||||
async function migrate() {
|
||||
|
||||
+20
-16
@@ -3,6 +3,7 @@ const options = require('./options');
|
||||
const utils = require('./utils');
|
||||
const notes = require('./notes');
|
||||
const audit_category = require('./audit_category');
|
||||
const data_encryption = require('./data_encryption');
|
||||
|
||||
async function createNewNote(parentNoteId, note, browserId) {
|
||||
const noteId = utils.newNoteId();
|
||||
@@ -46,10 +47,9 @@ async function createNewNote(parentNoteId, note, browserId) {
|
||||
'note_id': noteId,
|
||||
'note_title': note.note_title,
|
||||
'note_text': '',
|
||||
'note_clone_id': '',
|
||||
'date_created': now,
|
||||
'date_modified': now,
|
||||
'encryption': note.encryption
|
||||
'is_protected': note.is_protected
|
||||
});
|
||||
|
||||
await sql.insert("notes_tree", {
|
||||
@@ -64,13 +64,17 @@ async function createNewNote(parentNoteId, note, browserId) {
|
||||
return noteId;
|
||||
}
|
||||
|
||||
async function updateNote(noteId, newNote, browserId) {
|
||||
const origNoteDetail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]);
|
||||
async function encryptNote(note, ctx) {
|
||||
note.detail.note_title = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_title);
|
||||
note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_text);
|
||||
}
|
||||
|
||||
if (origNoteDetail.note_clone_id) {
|
||||
noteId = origNoteDetail.note_clone_id;
|
||||
async function updateNote(noteId, newNote, ctx) {
|
||||
if (newNote.detail.is_protected) {
|
||||
await encryptNote(newNote, ctx);
|
||||
}
|
||||
|
||||
const origNoteDetail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]);
|
||||
|
||||
const now = utils.nowTimestamp();
|
||||
|
||||
@@ -82,10 +86,10 @@ async function updateNote(noteId, newNote, browserId) {
|
||||
|
||||
await sql.doInTransaction(async () => {
|
||||
if (noteHistoryId) {
|
||||
await sql.execute("update notes_history set note_title = ?, note_text = ?, encryption = ?, date_modified_to = ? where note_history_id = ?", [
|
||||
await sql.execute("update notes_history set note_title = ?, note_text = ?, is_protected = ?, date_modified_to = ? where note_history_id = ?", [
|
||||
newNote.detail.note_title,
|
||||
newNote.detail.note_text,
|
||||
newNote.detail.encryption,
|
||||
newNote.detail.is_protected,
|
||||
now,
|
||||
noteHistoryId
|
||||
]);
|
||||
@@ -93,25 +97,25 @@ async function updateNote(noteId, newNote, browserId) {
|
||||
else {
|
||||
noteHistoryId = utils.randomString(16);
|
||||
|
||||
await sql.execute("insert into notes_history (note_history_id, note_id, note_title, note_text, encryption, date_modified_from, date_modified_to) " +
|
||||
await sql.execute("insert into notes_history (note_history_id, note_id, note_title, note_text, is_protected, date_modified_from, date_modified_to) " +
|
||||
"values (?, ?, ?, ?, ?, ?, ?)", [
|
||||
noteHistoryId,
|
||||
noteId,
|
||||
newNote.detail.note_title,
|
||||
newNote.detail.note_text,
|
||||
newNote.detail.encryption,
|
||||
newNote.detail.is_protected,
|
||||
now,
|
||||
now
|
||||
]);
|
||||
}
|
||||
|
||||
await sql.addNoteHistorySync(noteHistoryId);
|
||||
await addNoteAudits(origNoteDetail, newNote.detail, browserId);
|
||||
await addNoteAudits(origNoteDetail, newNote.detail, ctx.browserId);
|
||||
|
||||
await sql.execute("update notes set note_title = ?, note_text = ?, encryption = ?, date_modified = ? where note_id = ?", [
|
||||
await sql.execute("update notes set note_title = ?, note_text = ?, is_protected = ?, date_modified = ? where note_id = ?", [
|
||||
newNote.detail.note_title,
|
||||
newNote.detail.note_text,
|
||||
newNote.detail.encryption,
|
||||
newNote.detail.is_protected,
|
||||
now,
|
||||
noteId]);
|
||||
|
||||
@@ -147,10 +151,10 @@ async function addNoteAudits(origNote, newNote, browserId) {
|
||||
await sql.addAudit(audit_category.UPDATE_CONTENT, browserId, noteId);
|
||||
}
|
||||
|
||||
if (!origNote || newNote.encryption !== origNote.encryption) {
|
||||
const origEncryption = origNote ? origNote.encryption : null;
|
||||
if (!origNote || newNote.is_protected !== origNote.is_protected) {
|
||||
const origIsProtected = origNote ? origNote.is_protected : null;
|
||||
|
||||
await sql.addAudit(audit_category.ENCRYPTION, browserId, noteId, origEncryption, newNote.encryption);
|
||||
await sql.addAudit(audit_category.PROTECTED, browserId, noteId, origIsProtected, newNote.is_protected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const utils = require('./utils');
|
||||
|
||||
function setDataKey(req, decryptedDataKey) {
|
||||
@@ -7,8 +9,12 @@ function setDataKey(req, decryptedDataKey) {
|
||||
return req.session.protectedSessionId;
|
||||
}
|
||||
|
||||
function getProtectedSessionId(req) {
|
||||
return req.headers['x-protected-session-id'];
|
||||
}
|
||||
|
||||
function getDataKey(req) {
|
||||
const protectedSessionId = req.headers['x-protected-session-id'];
|
||||
const protectedSessionId = getProtectedSessionId(req);
|
||||
|
||||
if (protectedSessionId && req.session.protectedSessionId === protectedSessionId) {
|
||||
return req.session.decryptedDataKey;
|
||||
@@ -18,7 +24,14 @@ function getDataKey(req) {
|
||||
}
|
||||
}
|
||||
|
||||
function isProtectedSessionAvailable(req) {
|
||||
const protectedSessionId = getProtectedSessionId(req);
|
||||
|
||||
return protectedSessionId && req.session.protectedSessionId === protectedSessionId;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setDataKey,
|
||||
getDataKey
|
||||
getDataKey,
|
||||
isProtectedSessionAvailable
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
"use strict";
|
||||
|
||||
const protected_session = require('./protected_session');
|
||||
|
||||
module.exports = function(req) {
|
||||
const browserId = req.headers['x-browser-id'];
|
||||
|
||||
function isProtectedSessionAvailable() {
|
||||
return protected_session.isProtectedSessionAvailable(req);
|
||||
}
|
||||
|
||||
function getDataKey() {
|
||||
if (!isProtectedSessionAvailable()) {
|
||||
throw new Error("Protected session is not available");
|
||||
}
|
||||
|
||||
return protected_session.getDataKey(req);
|
||||
}
|
||||
|
||||
return {
|
||||
browserId,
|
||||
isProtectedSessionAvailable,
|
||||
getDataKey
|
||||
};
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -239,6 +239,10 @@ async function syncRequest(syncContext, method, uri, body) {
|
||||
if (isSyncSetup) {
|
||||
log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT);
|
||||
|
||||
if (SYNC_PROXY) {
|
||||
log.info("Sync proxy: " + SYNC_PROXY);
|
||||
}
|
||||
|
||||
setInterval(sync, 60000);
|
||||
|
||||
// kickoff initial sync immediately
|
||||
|
||||
Reference in New Issue
Block a user