Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
gr2m committed Apr 5, 2016
1 parent 4d525a8 commit 3bd6471
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 81 deletions.
59 changes: 32 additions & 27 deletions bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -24548,10 +24548,22 @@ function genKey(password, salt) {
});
});
}
function cryptoInit(password, modP) {
function cryptoInit(password, options) {
var db = this;
var key, pub;
var turnedOff = false;
var ignore = ['_id', '_rev']
var modP

if (typeof options === 'string' || Buffer.isBuffer(options)) {
modP = options
}
if (options && options.ignore) {
ignore = ignore.concat(options.ignore)
}
if (options && options.modP) {
modP = options.modP
}
return db.get(configId).catch(function (err) {
if (err.status === 404) {
var doc = {
Expand Down Expand Up @@ -24596,35 +24608,28 @@ function cryptoInit(password, modP) {
if (turnedOff) {
return doc;
}
var id, rev, attachments;
if ('_id' in doc) {
id = doc._id;
delete doc._id;
} else {
id = uuid.v4();
}
if ('_rev' in doc) {
rev = doc._rev;
delete doc._rev;
}
if ('_attachments' in doc) {
attachments = doc._attachments;
delete doc._attachments;
}
var nonce = crypto.randomBytes(12);
var data = JSON.stringify(doc);
var outDoc = {
_id: id,
nonce: nonce.toString('hex')
};
if (rev) {
outDoc._rev = rev;
}
if (attachments) {
outDoc._attachments = attachments;
// for loop performs better than .forEach etc
for (var i = 0, len = ignore.length; i < len; i++) {
outDoc[ignore[i]] = doc[ignore[i]]
delete doc[ignore[i]]
}
if (!outDoc._id) {
outDoc._id = uuid.v4()
}

// Encrypting attachments is complicated
// https://github.com/calvinmetcalf/crypto-pouch/pull/18#issuecomment-186402231
if (doc._attachments) {
throw new Error('Attachments cannot be encrypted. Use {ignore: "_attachments"} option')
}

var data = JSON.stringify(doc);
var cipher = chacha.createCipher(key, nonce);
cipher.setAAD(new Buffer(id));
cipher.setAAD(new Buffer(outDoc._id));
outDoc.data = cipher.update(data).toString('hex');
cipher.final();
outDoc.tag = cipher.getAuthTag().toString('hex');
Expand All @@ -24642,9 +24647,9 @@ function cryptoInit(password, modP) {
// parse it AFTER calling final
// you don't want to parse it if it has been manipulated
out = JSON.parse(out);
out._id = doc._id;
out._rev = doc._rev;
out._attachments = doc._attachments;
for (var i = 0, len = ignore.length; i < len; i++) {
out[ignore[i]] = doc[ignore[i]]
}
return out;
}
}
Expand Down
59 changes: 32 additions & 27 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,22 @@ function genKey(password, salt) {
});
});
}
function cryptoInit(password, modP) {
function cryptoInit(password, options) {
var db = this;
var key, pub;
var turnedOff = false;
var ignore = ['_id', '_rev']
var modP

if (typeof options === 'string' || Buffer.isBuffer(options)) {
modP = options
}
if (options && options.ignore) {
ignore = ignore.concat(options.ignore)
}
if (options && options.modP) {
modP = options.modP
}
return db.get(configId).catch(function (err) {
if (err.status === 404) {
var doc = {
Expand Down Expand Up @@ -64,35 +76,28 @@ function cryptoInit(password, modP) {
if (turnedOff) {
return doc;
}
var id, rev, attachments;
if ('_id' in doc) {
id = doc._id;
delete doc._id;
} else {
id = uuid.v4();
}
if ('_rev' in doc) {
rev = doc._rev;
delete doc._rev;
}
if ('_attachments' in doc) {
attachments = doc._attachments;
delete doc._attachments;
}
var nonce = crypto.randomBytes(12);
var data = JSON.stringify(doc);
var outDoc = {
_id: id,
nonce: nonce.toString('hex')
};
if (rev) {
outDoc._rev = rev;
}
if (attachments) {
outDoc._attachments = attachments;
// for loop performs better than .forEach etc
for (var i = 0, len = ignore.length; i < len; i++) {
outDoc[ignore[i]] = doc[ignore[i]]
delete doc[ignore[i]]
}
if (!outDoc._id) {
outDoc._id = uuid.v4()
}

// Encrypting attachments is complicated
// https://github.com/calvinmetcalf/crypto-pouch/pull/18#issuecomment-186402231
if (doc._attachments) {
throw new Error('Attachments cannot be encrypted. Use {ignore: "_attachments"} option')
}

var data = JSON.stringify(doc);
var cipher = chacha.createCipher(key, nonce);
cipher.setAAD(new Buffer(id));
cipher.setAAD(new Buffer(outDoc._id));
outDoc.data = cipher.update(data).toString('hex');
cipher.final();
outDoc.tag = cipher.getAuthTag().toString('hex');
Expand All @@ -110,9 +115,9 @@ function cryptoInit(password, modP) {
// parse it AFTER calling final
// you don't want to parse it if it has been manipulated
out = JSON.parse(out);
out._id = doc._id;
out._rev = doc._rev;
out._attachments = doc._attachments;
for (var i = 0, len = ignore.length; i < len; i++) {
out[ignore[i]] = doc[ignore[i]]
}
return out;
}
}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
"devDependencies": {
"browserify": "^13.0.0",
"memdown": "^1.0.0",
"pouchdb": "^5.2.1",
"pouchdb": "^5.3.1",
"tap-spec": "^4.1.1",
"tape": "^4.4.0",
"uglify-js": "^2.4.15"
"tape": "^4.5.1",
"uglify-js": "^2.6.2"
}
}
44 changes: 26 additions & 18 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ db.crypto(password).then(function (publicKey) {
db.removeCrypto(); // will no longer encrypt decrypt your data
```

It currently encrypts with the [Chacha20-Poly1305](https://github.com/calvinmetcalf/chacha20poly1305) algorithm, but this may be changed
It currently encrypts with the [Chacha20-Poly1305](https://github.com/calvinmetcalf/chacha20poly1305) algorithm, but this may be changed
to AES256-GCM when Node 0.12.0 drops.

**Note**: Due to performance reasons this module does not encrypt attachments ([#18](https://github.com/calvinmetcalf/crypto-pouch/pull/18))
**Note**: Attachments cannot be encrypted at this point. Use `{ignore: '_attachments'}` to leave attachments unencrypted. Also note that `db.putAttachment` / `db.getAttachment` are not supported. Use `db.put` and `db.get({binary: true, attachment: true})` instead. ([#18](https://github.com/calvinmetcalf/crypto-pouch/issues/13))

Usage
-------
Expand All @@ -38,15 +38,23 @@ API
--------


### db.crypto(password [, diffieHellman])
### db.crypto(password [, options])

Set up encryption on the database. Returns a promise.

If the second argument is a string, it is taken to be a Diffie-Hellman ModP group and if a buffer then a prime
and the password is interpreted as a Diffie-Hellman public key. If so, the public key
for use with the database is returned; you can use that to calculate the shared secret
If the second argument is a string, it is taken to be a Diffie-Hellman ModP group and if a buffer then a prime
and the password is interpreted as a Diffie-Hellman public key. If so, the public key
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 second argument is an object:

- `options.modp`
see above

- `options.ignore`
String or Array of Strings of properties that will not be encrypted.


### db.removeCrypto()

Expand All @@ -55,22 +63,22 @@ Disables encryption on the database.
Details
===

If you replicate to another database, it will decrypt before sending it to
the external one. So make sure that one also has a password set as well if you want
If you replicate to another database, it will decrypt before sending it to
the external one. So make sure that one also has a password set as well if you want
it encrypted too.

If you change the name of a document, it will throw an error when you try
to decrypt it. If you manually move a document from one database to another,
it will not decrypt correctly. If you need to decrypt it a file manually
you will find a local doc named `_local/crypto` in the database. This doc has a field
named `salt` which is a hex-encoded buffer. Run on your password with that as salt
for 1000 iterations to generate a 32 byte (256 bit) key; that is the key
If you change the name of a document, it will throw an error when you try
to decrypt it. If you manually move a document from one database to another,
it will not decrypt correctly. If you need to decrypt it a file manually
you will find a local doc named `_local/crypto` in the database. This doc has a field
named `salt` which is a hex-encoded buffer. Run on your password with that as salt
for 1000 iterations to generate a 32 byte (256 bit) key; that is the key
for decoding documents.

Each document has 3 relevant fields: `data`, `nonce`, and `tag`.
`nonce` is the initialization vector to give to chacha20 in addition to the key
you generated. Pass the document `_id` as additional authenticated data and the tag
as the auth tag and then decrypt the data. If it throws an error, then you either
Each document has 3 relevant fields: `data`, `nonce`, and `tag`.
`nonce` is the initialization vector to give to chacha20 in addition to the key
you generated. Pass the document `_id` as additional authenticated data and the tag
as the auth tag and then decrypt the data. If it throws an error, then you either
screwed up or somebody modified the data.

Examples
Expand Down
58 changes: 52 additions & 6 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,36 @@ test('changes', function (t) {
t.error(e);
});
});
test('attachments', function (t) {
test('ignore: _attachments', function (t) {
t.plan(1);
var dbName = 'six';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password').then(function () {
db.crypto('password', {ignore: '_attachments'}).then(function () {
return db.put({
_id: 'id-12345678',
_attachments: {
'att.txt': {
content_type: 'text/plain',
data: 'TGVnZW5kYXJ5IGhlYXJ0cywgdGVhciB1cyBhbGwgYXBhcnQKTWFrZS' +
'BvdXIgZW1vdGlvbnMgYmxlZWQsIGNyeWluZyBvdXQgaW4gbmVlZA=='
}
}
});
}).then(function () {
return db.get('id-12345678', {
attachments: true,
binary: true
});
}).then(function (doc) {
t.ok(Buffer.isBuffer(doc._attachments['att.txt'].data), 'returns _attachments as Buffers');
})
.catch(t.error);
})
test('modp: "modp5", ignore: "_attachments"', function (t) {
t.plan(1);
var dbName = 'seven';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password', {modp: 'modp5', ignore: '_attachments'}).then(function () {
return db.put({
_id: 'id-12345678',
_attachments: {
Expand All @@ -153,14 +178,35 @@ test('attachments', function (t) {
'BvdXIgZW1vdGlvbnMgYmxlZWQsIGNyeWluZyBvdXQgaW4gbmVlZA=='
}
}
})
});
}).then(function () {
return db.get('id-12345678', {
attachments: true,
binary: true
})
});
}).then(function (doc) {
t.ok(Buffer.isBuffer(doc._attachments['att.txt'].data), 'returns _attachtments as Buffers')
t.ok(Buffer.isBuffer(doc._attachments['att.txt'].data), 'returns _attachments as Buffers');
})
.catch(t.error);
})
test('throws error when document has attachments', function (t) {
t.plan(1);
var dbName = 'eight';
var db = new PouchDB(dbName, {db: memdown});
db.crypto('password').then(function () {
return db.put({
_id: 'id-12345678',
_attachments: {
'att.txt': {
content_type: 'text/plain',
data: 'TGVnZW5kYXJ5IGhlYXJ0cywgdGVhciB1cyBhbGwgYXBhcnQKTWFrZS' +
'BvdXIgZW1vdGlvbnMgYmxlZWQsIGNyeWluZyBvdXQgaW4gbmVlZA=='
}
}
});
}).then(function () {
t.error('does not throw error');
}).catch(function (e) {
t.ok(/Attachments cannot be encrypted/.test(e.message), 'throws error');
})
.catch(t.error)
})

0 comments on commit 3bd6471

Please sign in to comment.