UNPKG

ara-crypto

Version:

Cryptographic functions used in Ara modules

163 lines (131 loc) 3.94 kB
const increment = require('increment-buffer') const isBuffer = require('is-buffer') const through = require('through2') const split = require('split-buffer') /* eslint-disable camelcase */ const { crypto_secretbox_NONCEBYTES, crypto_secretbox_KEYBYTES, crypto_secretbox_MACBYTES, crypto_secretbox_easy, } = require('./sodium') const kBoxHeaderSize = 2 + (2 * crypto_secretbox_MACBYTES) const kBoxBufferSize = 4 * 1024 /** * "Boxes", or encrypts, a buffer from a 32 byte encryption key * and a 24-byte nonce. * * @public * @param {Buffer} buffer * @param {Object} opts * @param {Buffer} opts.buffer * @param {?(Buffer)} opts.secret * @param {?(Buffer)} opts.nonce * @param {?(Buffer)} opts.key * @return {Buffer} * @throws TypeError */ function box(buffer, opts) { if (!opts || 'object' !== typeof opts) { throw new TypeError('crypto.box: Expecting object.') } const { secret } = opts let { nonce, key } = opts if (!buffer || false === isBuffer(buffer)) { throw new TypeError('crypto.box: Expecting buffer.') } if (isBuffer(secret) && secret.length >= crypto_secretbox_KEYBYTES) { key = secret.slice(0, crypto_secretbox_KEYBYTES) nonce = isBuffer(opts.nonce) ? opts.nonce : secret.slice(crypto_secretbox_KEYBYTES) } if (false === isBuffer(nonce)) { throw new TypeError('crypto.box: Expecting nonce.') } if (false === isBuffer(key)) { throw new TypeError('crypto.box: Expecting secret key.') } // truncate key and nonce nonce = nonce.slice(0, crypto_secretbox_NONCEBYTES) key = key.slice(0, crypto_secretbox_KEYBYTES) // ephemeral nonces used for header and body buffer boxing const nonces = [ copy(nonce), increment(copy(nonce)) ] // length(2) + MAC(16) == 18 const header = Buffer.alloc(2 + crypto_secretbox_MACBYTES) // boxed buffer cipher text const body = Buffer.alloc(crypto_secretbox_MACBYTES + buffer.length) // output buffer frames const frames = [ Buffer.alloc(crypto_secretbox_MACBYTES + header.length), Buffer.alloc(buffer.length), ] // write buffer length into header header.writeUInt16BE(buffer.length, 0) // box message buffer crypto_secretbox_easy(body, buffer, nonces[1], key) // copy MAC into header after length (offset 2) body.copy( header, 2, 0, crypto_secretbox_MACBYTES ) // copy boxed message buffer into frames[1] body.copy( frames[1], 0, crypto_secretbox_MACBYTES, crypto_secretbox_MACBYTES + buffer.length ) // box header buffer into frame[0] based on current nonces[0] and key crypto_secretbox_easy(frames[0], header, nonces[0], key) return Object.assign(Buffer.concat(frames), { nonce: nonces[1] }) } /** * Creates a transform stream that "boxes" messages * written to it. * * @public * @param {Object} opts * @param {?(Buffer)} opts.secret * @param {?(Buffer)} opts.nonce * @param {?(Buffer)} opts.key * @return {Stream} * @throws TypeError */ function createBoxStream(opts) { if (!opts || 'object' !== typeof opts) { throw new TypeError('crypto.box: Expecting object.') } // create new reference /* eslint-disable-next-line no-param-reassign */ opts = { ...opts } if (false === isBuffer(opts.nonce)) { throw new TypeError('crypto.createBoxStream: Expecting nonce.') } return through(transform) function transform(buffer, enc, done) { const chunks = split(buffer, kBoxBufferSize) for (const chunk of chunks) { const offset = kBoxHeaderSize const boxed = box(chunk, opts) const nonce = increment(copy(boxed.nonce)) const head = boxed.slice(0, offset) const body = boxed.slice(offset) this.push(head) this.push(body) Object.assign(opts, { nonce }) } done(null) } } function copy(x) { const y = Buffer.allocUnsafe(x.length) x.copy(y, 0, 0, x.length) return y } module.exports = { createBoxStream, box, }