diff --git a/public/javascripts/encryption.js b/public/javascripts/encryption.js index 3df43aabf..b0017d2aa 100644 --- a/public/javascripts/encryption.js +++ b/public/javascripts/encryption.js @@ -131,11 +131,9 @@ const encryption = (function() { function resetEncryptionSession() { dataKey = null; - if (noteEditor.getCurrentNote().detail.encryption > 0) { - // most secure solution - guarantees nothing remained in memory - // since this expires because user doesn't use the app, it shouldn't be disruptive - window.location.reload(true); - } + // most secure solution - guarantees nothing remained in memory + // since this expires because user doesn't use the app, it shouldn't be disruptive + window.location.reload(true); } function isEncryptionAvailable() { diff --git a/routes/api/login.js b/routes/api/login.js index 5945115a3..2c15ee07a 100644 --- a/routes/api/login.js +++ b/routes/api/login.js @@ -6,8 +6,11 @@ const options = require('../../services/options'); const utils = require('../../services/utils'); const migration = require('../../services/migration'); const SOURCE_ID = require('../../services/source_id'); +const auth = require('../../services/auth'); +const password_encryption = require('../../services/password_encryption'); +const protected_session = require('../../services/protected_session'); -router.post('', async (req, res, next) => { +router.post('/sync', async (req, res, next) => { const timestamp = req.body.timestamp; const now = utils.nowTimestamp(); @@ -41,4 +44,25 @@ router.post('', async (req, res, next) => { }); }); +// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) +router.post('protected', auth.checkApiAuth, async (req, res, next) => { + const password = req.body.password; + + if (!await password_encryption.verifyPassword(password)) { + return { + success: false, + message: "Given current password doesn't match hash" + }; + } + + const decryptedDataKey = password_encryption.getDecryptedDataKey(password); + + const protectedSessionId = protected_session.setDataKey(req, decryptedDataKey); + + res.send({ + success: true, + protectedSessionId: protectedSessionId + }); +}); + module.exports = router; \ No newline at end of file diff --git a/routes/api/migration.js b/routes/api/migration.js index 8894cb907..b41c9f4b4 100644 --- a/routes/api/migration.js +++ b/routes/api/migration.js @@ -8,8 +8,8 @@ const migration = require('../../services/migration'); router.get('', auth.checkApiAuthWithoutMigration, async (req, res, next) => { res.send({ - 'db_version': parseInt(await options.getOption('db_version')), - 'app_db_version': migration.APP_DB_VERSION + db_version: parseInt(await options.getOption('db_version')), + app_db_version: migration.APP_DB_VERSION }); }); @@ -17,7 +17,7 @@ router.post('', auth.checkApiAuthWithoutMigration, async (req, res, next) => { const migrations = await migration.migrate(); res.send({ - 'migrations': migrations + migrations: migrations }); }); diff --git a/services/change_password.js b/services/change_password.js index 22c3826b9..7c565a10a 100644 --- a/services/change_password.js +++ b/services/change_password.js @@ -5,52 +5,22 @@ const options = require('./options'); const my_scrypt = require('./my_scrypt'); const utils = require('./utils'); const audit_category = require('./audit_category'); -const crypto = require('crypto'); -const aesjs = require('./aes'); +const password_encryption = require('./password_encryption'); async function changePassword(currentPassword, newPassword, req) { - const current_password_hash = utils.toBase64(await my_scrypt.getVerificationHash(currentPassword)); - - if (current_password_hash !== await options.getOption('password_verification_hash')) { + if (!await password_encryption.verifyPassword(currentPassword)) { return { - 'success': false, - 'message': "Given current password doesn't match hash" + success: false, + message: "Given current password doesn't match hash" }; } - const currentPasswordDerivedKey = await my_scrypt.getPasswordDerivedKey(currentPassword); - const newPasswordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(newPassword)); - const newPasswordEncryptionKey = await my_scrypt.getPasswordDerivedKey(newPassword); + const newPasswordDerivedKey = await my_scrypt.getPasswordDerivedKey(newPassword); - function decrypt(encryptedBase64) { - const encryptedBytes = utils.fromBase64(encryptedBase64); + const decryptedDataKey = await password_encryption.getDecryptedDataKey(currentPassword); - const aes = getAes(currentPasswordDerivedKey); - return aes.decrypt(encryptedBytes).slice(4); - } - - function encrypt(plainText) { - const aes = getAes(newPasswordEncryptionKey); - - const plainTextBuffer = Buffer.from(plainText, 'latin1'); - - const digest = crypto.createHash('sha256').update(plainTextBuffer).digest().slice(0, 4); - - const encryptedBytes = aes.encrypt(Buffer.concat([digest, plainTextBuffer])); - - return utils.toBase64(encryptedBytes); - } - - function getAes(key) { - return new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); - } - - const encryptedDataKey = await options.getOption('encrypted_data_key'); - - const decryptedDataKey = decrypt(encryptedDataKey); - - const newEncryptedDataKey = encrypt(decryptedDataKey); + const newEncryptedDataKey = password_encryption.encryptDataKey(newPasswordDerivedKey, decryptedDataKey); await sql.doInTransaction(async () => { await options.setOption('encrypted_data_key', newEncryptedDataKey); @@ -61,8 +31,8 @@ async function changePassword(currentPassword, newPassword, req) { }); return { - 'success': true, - 'new_encrypted_data_key': newEncryptedDataKey + success: true, + new_encrypted_data_key: newEncryptedDataKey }; } diff --git a/services/password_encryption.js b/services/password_encryption.js new file mode 100644 index 000000000..276c9f4fa --- /dev/null +++ b/services/password_encryption.js @@ -0,0 +1,53 @@ +const options = require('./options'); +const my_scrypt = require('./my_scrypt'); +const utils = require('./utils'); +const crypto = require('crypto'); +const aesjs = require('./aes'); + +async function verifyPassword(password) { + const givenPasswordHash = utils.toBase64(await my_scrypt.getVerificationHash(password)); + + const dbPasswordHash = await options.getOption('password_verification_hash'); + + return givenPasswordHash === dbPasswordHash; +} + +function decryptDataKey(passwordDerivedKey, encryptedBase64) { + const encryptedBytes = utils.fromBase64(encryptedBase64); + + const aes = getAes(passwordDerivedKey); + return aes.decrypt(encryptedBytes).slice(4); +} + +function encryptDataKey(passwordDerivedKey, plainText) { + const aes = getAes(passwordDerivedKey); + + const plainTextBuffer = Buffer.from(plainText, 'latin1'); + + const digest = crypto.createHash('sha256').update(plainTextBuffer).digest().slice(0, 4); + + const encryptedBytes = aes.encrypt(Buffer.concat([digest, plainTextBuffer])); + + return utils.toBase64(encryptedBytes); +} + +async function getDecryptedDataKey(password) { + const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); + + const encryptedDataKey = await options.getOption('encrypted_data_key'); + + const decryptedDataKey = decryptDataKey(passwordDerivedKey, encryptedDataKey); + + return decryptedDataKey; +} + +function getAes(key) { + return new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); +} + +module.exports = { + verifyPassword, + decryptDataKey, + encryptDataKey, + getDecryptedDataKey +}; \ No newline at end of file diff --git a/services/protected_session.js b/services/protected_session.js new file mode 100644 index 000000000..8ee57abb9 --- /dev/null +++ b/services/protected_session.js @@ -0,0 +1,22 @@ +const utils = require('./utils'); + +function setDataKey(req, decryptedDataKey) { + req.session.decryptedDataKey = decryptedDataKey; + req.session.protectedSessionId = utils.randomSecureToken(32); + + return req.session.protectedSessionId; +} + +function getDataKey(req, protectedSessionId) { + if (protectedSessionId && req.session.protectedSessionId === protectedSessionId) { + return req.session.decryptedDataKey; + } + else { + return null; + } +} + +module.exports = { + setDataKey, + getDataKey +}; \ No newline at end of file diff --git a/services/sync.js b/services/sync.js index 045ecdcf6..4e6922669 100644 --- a/services/sync.js +++ b/services/sync.js @@ -82,7 +82,7 @@ async function login() { const syncContext = { cookieJar: rp.jar() }; - const resp = await syncRequest(syncContext, 'POST', '/api/login', { + const resp = await syncRequest(syncContext, 'POST', '/api/login/sync', { timestamp: timestamp, dbVersion: migration.APP_DB_VERSION, hash: hash