From e83d55feadf6f81848b1e50c131258685a638692 Mon Sep 17 00:00:00 2001 From: Andrew Schamp Date: Thu, 31 Mar 2016 08:36:18 -0500 Subject: [PATCH 1/2] Check if key is correct, throw exception if not. --- index.js | 51 +++++++++++++++++++++++++++++++++++++++++++-------- test.js | 10 ++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index a298d44a..d5e69f8b 100644 --- a/index.js +++ b/index.js @@ -3,9 +3,16 @@ var crypto = require('crypto'); var chacha = require('chacha'); var PouchPromise = require('pouchdb-promise'); var configId = '_local/crypto'; +var cryptoCheckId = '_local/cryptoCheck'; +var cryptoCheckVal = 'cryptoCheck'; var transform = require('transform-pouch').transform; var uuid = require('node-uuid'); -function genKey(password, salt) { + +function InvalidPasswordException () { + this.name = 'InvalidPasswordException'; +} + +function genKey (password, salt) { return new PouchPromise(function (resolve, reject) { crypto.pbkdf2(password, salt, 1000, 256 / 8, function (err, key) { password = null; @@ -16,7 +23,7 @@ function genKey(password, salt) { }); }); } -function cryptoInit(password, modP) { +function cryptoInit (password, modP) { var db = this; var key, pub; var turnedOff = false; @@ -56,11 +63,39 @@ function cryptoInit(password, modP) { randomize(key); turnedOff = true; }; - if (pub) { - return pub; - } + + // a document to test if we can successfully decrypt + var cryptoCheckDoc = { + _id: cryptoCheckId, + val: cryptoCheckVal + }; + return db.get(cryptoCheckId).catch(function (err) { + // if the check document doesn't exist, this is the first time we're encrypting this DB, add the document if it isn't there, + // must manually encrypt, because pouchdb.tranform() doesn't operate on _local documents + // but we want this to remain a local document, so it isn't replicated, etc. + return db.put(encrypt(cryptoCheckDoc)).then(function () { + return cryptoCheckDoc; + }); + }).then(function (doc) { + // this will run even if we just put it in, but it should work just fine + return decrypt(doc); + }).catch(function (err) { + // first, catch a JSON parse error in the decrypt function + if (err.name === 'SyntaxError') { + throw new InvalidPasswordException(); + } + }).then(function (_cryptoCheckDoc) { + // if, somehow, the decrypted doc is valid JSON, make sure it matches the expected one + if (_cryptoCheckDoc.val !== cryptoCheckDoc.val) { + throw new InvalidPasswordException(); + } + }).then(function () { + if (pub) { + return pub; + } + }); }); - function encrypt(doc) { + function encrypt (doc) { if (turnedOff) { return doc; } @@ -91,7 +126,7 @@ function cryptoInit(password, modP) { outDoc.tag = cipher.getAuthTag().toString('hex'); return outDoc; } - function decrypt(doc) { + function decrypt (doc) { if (turnedOff || !doc.nonce || !doc._id || !doc.tag || !doc.data) { return doc; } @@ -108,7 +143,7 @@ function cryptoInit(password, modP) { return out; } } -function randomize(buf) { +function randomize (buf) { var len = buf.length; var data = crypto.randomBytes(len); var i = -1; diff --git a/test.js b/test.js index 5f057521..ba4b3886 100644 --- a/test.js +++ b/test.js @@ -34,6 +34,16 @@ test('reopen', function (t) { t.equals(resp.foo, 'bar', 'decrypts data'); }); }); +test('wrong_pass', function (t) { + t.plan(1); + var dbName = 'one'; + var db = new PouchDB(dbName, {db: memdown}); + db.crypto('bad_password').then(function (resp) { + t.fail('opened database with bad password') + }).catch(function (r) { + t.equals(r.name, 'InvalidPasswordException', ' fails setting up invalid password.') + }) +}); var pub, dh; test('dh', function (t) { t.plan(5); From 21659668ead6e9c80466373beecbf1a6b4cf6a63 Mon Sep 17 00:00:00 2001 From: Andrew Schamp Date: Thu, 31 Mar 2016 08:39:44 -0500 Subject: [PATCH 2/2] Add documentation for new exception. --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index aab7898e..07c042b3 100644 --- a/readme.md +++ b/readme.md @@ -45,6 +45,7 @@ and the password is interpreted as a Diffie-Hellman public key. If so, the publi for use with the database is returned; you can use that to calculate the shared secret which is needed for subsequently opening the data set. +If the given password is invalid, throws an InvalidPasswordException(). ### db.removeCrypto()