server side WIP - saving encrypted note now works, changing terminology of "encrypted note" to "protected note"

This commit is contained in:
azivner
2017-11-14 21:54:12 -05:00
parent c18799b938
commit ff411f00b1
19 changed files with 208 additions and 87 deletions
+1 -1
View File
@@ -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'
+45 -2
View File
@@ -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
};
+1 -1
View File
@@ -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
View File
@@ -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);
}
}
+15 -2
View File
@@ -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
};
+25
View File
@@ -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
+4
View File
@@ -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