From 0f3d11bcbd5945120909ad08562409807b50544e Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 16 Apr 2016 09:11:57 +0300 Subject: [PATCH 1/7] Update package.json --- .eslintrc | 3 --- .gitignore | 5 +++++ package.json | 36 +++++++++++++++++++++++------------- 3 files changed, 28 insertions(+), 16 deletions(-) delete mode 100644 .eslintrc create mode 100644 .gitignore diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a755cdb..0000000 --- a/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["standard"] -} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8afa17e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.nyc_output +coverage +node_modules + +npm-debug.log diff --git a/package.json b/package.json index a5c8caf..f298af0 100644 --- a/package.json +++ b/package.json @@ -2,29 +2,39 @@ "name": "cipher-base", "version": "1.0.2", "description": "abstract base class for crypto-streams", - "main": "index.js", - "scripts": { - "test": "node test.js | tspec" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/crypto-browserify/cipher-base.git" - }, "keywords": [ "cipher", "stream" ], - "author": "Calvin Metcalf ", - "license": "MIT", + "homepage": "https://github.com/crypto-browserify/cipher-base#readme", "bugs": { "url": "https://github.com/crypto-browserify/cipher-base/issues" }, - "homepage": "https://github.com/crypto-browserify/cipher-base#readme", + "license": "MIT", + "author": "Calvin Metcalf ", + "files": [ + "index.js" + ], + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/crypto-browserify/cipher-base.git" + }, + "scripts": { + "coverage": "nyc node test.js", + "lint": "standard", + "test": "npm run lint && npm run unit", + "unit": "node test.js" + }, "dependencies": { "inherits": "^2.0.1" }, "devDependencies": { - "tap-spec": "^4.1.0", - "tape": "^4.2.0" + "nyc": "^8.3.2", + "standard": "*", + "tape": "^4.6.2" + }, + "engines": { + "node": ">=4" } } From 60021b8e234f632d87fb8b7c72953426ca63a143 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 16 Apr 2016 09:12:38 +0300 Subject: [PATCH 2/7] Update travis config --- .travis.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb83acd..b07fd55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,15 @@ +sudo: false language: node_js node_js: - - "0.11" - - "0.10" - - "0.12" - - "iojs" + - "4" + - "5" + - "6" + - "7" +env: + matrix: + - TEST_SUITE=unit +matrix: + include: + - node_js: "6" + env: TEST_SUITE=lint +script: npm run-script $TEST_SUITE From 8f8bf954be583f854311d54624e08e225a02d631 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 1 Nov 2016 11:54:29 +0300 Subject: [PATCH 3/7] Rename readme to README --- readme.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.md => README.md (100%) diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md From 4979a678c70e3b43785c342337e41f83add885c9 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 1 Nov 2016 13:09:23 +0300 Subject: [PATCH 4/7] Move test.js to test/index.js --- package.json | 4 ++-- test.js => test/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename test.js => test/index.js (98%) diff --git a/package.json b/package.json index f298af0..29731ed 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,10 @@ "url": "https://github.com/crypto-browserify/cipher-base.git" }, "scripts": { - "coverage": "nyc node test.js", + "coverage": "nyc node test/*.js", "lint": "standard", "test": "npm run lint && npm run unit", - "unit": "node test.js" + "unit": "node test/*.js" }, "dependencies": { "inherits": "^2.0.1" diff --git a/test.js b/test/index.js similarity index 98% rename from test.js rename to test/index.js index 57d144a..dddbf8f 100644 --- a/test.js +++ b/test/index.js @@ -1,5 +1,5 @@ var test = require('tape') -var CipherBase = require('./') +var CipherBase = require('../') var inherits = require('inherits') test('basic version', function (t) { From c25cbc835f6c567f3943ba212467907903194bbe Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Tue, 1 Nov 2016 09:53:39 +0300 Subject: [PATCH 5/7] Update tests --- test/index.js | 504 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 418 insertions(+), 86 deletions(-) diff --git a/test/index.js b/test/index.js index dddbf8f..62b8abc 100644 --- a/test/index.js +++ b/test/index.js @@ -1,108 +1,440 @@ -var test = require('tape') +'use strict' +var test = require('tape').test var CipherBase = require('../') -var inherits = require('inherits') -test('basic version', function (t) { - inherits(Cipher, CipherBase) - function Cipher () { - CipherBase.call(this) - } - Cipher.prototype._update = function (input) { - t.ok(Buffer.isBuffer(input)) - return input - } - Cipher.prototype._final = function () { - // noop +var utf8text = 'УТФ-8 text' +var utf8buf = Buffer.from(utf8text, 'utf8') +function noop () {} + +function createCipher (t) { t.cipher = new CipherBase(true) } +function createDeciper (t) { t.decipher = new CipherBase(false) } + +function beforeEach (t) { + var fns = Array.prototype.slice.call(arguments, 1) + var _test = t.test + t.test = function (name, callback) { + _test(name, function (t) { + for (var i = 0; i < fns.length; ++i) t = fns[i](t) || t + callback(t) + }) } - var cipher = new Cipher() - var utf8 = 'abc123abcd' - var update = cipher.update(utf8, 'utf8', 'base64') + cipher.final('base64') - var string = (new Buffer(update, 'base64')).toString() - t.equals(utf8, string) +} + +test('CipherBase#_isAuthenticatedMode', function (t) { + beforeEach(t, createCipher) + + t.test('is not implemented', function (t) { + t.throws(function () { + t.cipher._isAuthenticatedMode() + }, /^Error: _isAuthenticatedMode is not implemented$/) + t.end() + }) + t.end() }) -test('hash mode', function (t) { - inherits(Cipher, CipherBase) - function Cipher () { - CipherBase.call(this, 'finalName') - this._cache = [] - } - Cipher.prototype._update = function (input) { - t.ok(Buffer.isBuffer(input)) - this._cache.push(input) - } - Cipher.prototype._final = function () { - return Buffer.concat(this._cache) - } - var cipher = new Cipher() - var utf8 = 'abc123abcd' - var update = cipher.update(utf8, 'utf8').finalName('base64') - var string = (new Buffer(update, 'base64')).toString() - t.equals(utf8, string) +test('CipherBase#_transform', function (t) { + beforeEach(t, createCipher) + + t.test('should use CipherBase#update', function (t) { + t.plan(4) + t.cipher.update = function () { + t.same(arguments.length, 2) + t.same(arguments[0], utf8text) + t.same(arguments[1], 'utf8') + } + t.cipher._transform(utf8text, 'utf8', function (err) { + t.same(err, null) + }) + t.end() + }) + + t.test('should handle error in CipherBase#update', function (t) { + t.plan(1) + var err = new Error('hey') + t.cipher.update = function () { throw err } + t.cipher._transform(Buffer.allocUnsafe(0), undefined, function (_err) { + t.true(_err === err) + }) + t.end() + }) + t.end() }) -test('hash mode as stream', function (t) { - inherits(Cipher, CipherBase) - function Cipher () { - CipherBase.call(this, 'finalName') - this._cache = [] - } - Cipher.prototype._update = function (input) { - t.ok(Buffer.isBuffer(input)) - this._cache.push(input) - } - Cipher.prototype._final = function () { - return Buffer.concat(this._cache) - } - var cipher = new Cipher() - cipher.on('error', function (e) { - t.notOk(e) + +test('CipherBase#_flush', function (t) { + beforeEach(t, createCipher) + + t.test('should use CipherBase#final', function (t) { + t.plan(2) + var buffer = Buffer.allocUnsafe(0) + t.cipher.push = function (data) { t.true(data === buffer) } + t.cipher.final = function () { return buffer } + t.cipher._flush(function (err) { t.same(err, null) }) + t.end() + }) + + t.test('should handle errors in CipherBase#final', function (t) { + t.plan(1) + var err = new Error('hey') + t.cipher.final = function () { throw err } + t.cipher._flush(function (_err) { t.true(_err === err) }) + t.end() }) - var utf8 = 'abc123abcd' - cipher.end(utf8, 'utf8') - var update = cipher.read().toString('base64') - var string = (new Buffer(update, 'base64')).toString() - t.equals(utf8, string) t.end() }) -test('encodings', function (t) { - inherits(Cipher, CipherBase) - function Cipher () { - CipherBase.call(this) - } - Cipher.prototype._update = function (input) { - return input + +test('CipherBase#update', function (t) { + beforeEach(t, createCipher) + + t.test('data should be buffer or string', function (t) { + t.throws(function () { + t.cipher.update(null) + }, /^TypeError: Cipher data must be a string or a buffer$/) + t.end() + }) + + t.test('should throw error after CipherBase#final', function (t) { + t.cipher._final = noop + t.cipher.final() + t.throws(function () { + t.cipher.update(Buffer.allocUnsafe(0)) + }, /^Error: Trying to add data in unsupported state$/) + t.end() + }) + + t.test('should use CipherBase#_update', function (t) { + t.plan(1) + t.cipher._update = t.pass + t.cipher.update(Buffer.allocUnsafe(0)) + t.end() + }) + + t.test('inputEncoding is utf8 by default', function (t) { + t.plan(1) + t.cipher._update = function (data) { t.same(data, utf8buf) } + t.cipher.update(utf8text) + t.end() + }) + + t.test('inputEncoding is defined', function (t) { + t.plan(1) + t.cipher._update = function (data) { t.same(data, utf8buf) } + t.cipher.update(utf8buf.toString('hex'), 'hex') + t.end() + }) + + t.test('outputEncoding is buffer by default', function (t) { + t.cipher._update = function () { return Buffer.allocUnsafe(0) } + var output = t.cipher.update(Buffer.allocUnsafe(0), undefined) + t.true(Buffer.isBuffer(output)) + t.end() + }) + + t.test('outputEncoding is defined', function (t) { + t.cipher._update = function () { return utf8buf } + var output = t.cipher.update(Buffer.allocUnsafe(0), undefined, 'utf8') + t.same(output, utf8text) + t.end() + }) + + t.test('if outputEncoding is defined should be same as in previous time', function (t) { + t.cipher._update = function () { return Buffer.allocUnsafe(0) } + t.cipher.update(Buffer.allocUnsafe(0), undefined, 'utf8') + t.throws(function () { + t.cipher.update(Buffer.allocUnsafe(0), undefined, 'hex') + }, /^Error: Cannot change encoding$/) + t.end() + }) + + t.end() +}) + +test('CipherBase#_update', function (t) { + beforeEach(t, createCipher) + + t.test('is not implemented', function (t) { + t.throws(function () { + t.cipher._update() + }, /^Error: _update is not implemented$/) + t.end() + }) + + t.end() +}) + +test('CipherBase#final', function (t) { + beforeEach(t, createCipher, createDeciper) + + t.test('should throw error on second call', function (t) { + t.plan(2) + t.cipher._final = noop + t.cipher._isAuthenticatedMode = function () { return true } + t.cipher.final() + t.throws(function () { + t.cipher.final() + }, /^Error: Unsupported state or unable to authenticate data$/) + t.cipher._isAuthenticatedMode = function () { return false } + t.throws(function () { + t.cipher.final() + }, /^Error: Unsupported state$/) + t.end() + }) + + t.test('should call CipherBase#_final', function (t) { + t.plan(1) + t.cipher._final = t.pass + t.cipher.final() + t.end() + }) + + t.test('outputEncoding is buffer by default', function (t) { + t.cipher._final = function () { return Buffer.allocUnsafe(0) } + var output = t.cipher.final() + t.true(Buffer.isBuffer(output)) + t.end() + }) + + t.test('outputEncoding is defined', function (t) { + t.cipher._final = function () { return utf8buf } + var output = t.cipher.final('utf8') + t.same(output, utf8text) + t.end() + }) + + t.test('if outputEncoding is defined should be same as in previous time', function (t) { + t.cipher._update = function () { return Buffer.allocUnsafe(0) } + t.cipher.update(Buffer.allocUnsafe(0), undefined, 'utf8') + t.cipher._final = function () { return Buffer.allocUnsafe(0) } + t.throws(function () { + t.cipher.final('hex') + }, /^Error: Cannot change encoding$/) + t.end() + }) + + t.test('should destroy _authTag', function (t) { + var tagbuf = Buffer.from([0x42]) + t.decipher._authTag = tagbuf // hack, because setAuthTag will throw error + t.decipher._isAuthenticatedMode = function () { return true } + t.decipher._final = noop + t.decipher.final() + t.same(tagbuf, Buffer.from([0x00])) + t.same(t.decipher._authTag, null) + t.end() + }) + + t.end() +}) + +test('CipherBase#_final', function (t) { + beforeEach(t, createCipher) + + t.test('is not implemented', function (t) { + t.throws(function () { + t.cipher._final() + }, /^Error: _final is not implemented$/) + t.end() + }) + + t.end() +}) + +test('CipherBase#setAutoPadding', function (t) { + beforeEach(t, createCipher) + + t.test('should throw error after CipherBase#final', function (t) { + t.cipher._isAuthenticatedMode = noop + t.cipher._final = noop + t.cipher.final() + t.throws(function () { + t.cipher.setAutoPadding() + }, /^Error: Attempting to set auto padding in unsupported state$/) + t.end() + }) + + t.test('should call CipherBase#_setAutoPadding', function (t) { + t.plan(1) + t.cipher._setAutoPadding = t.pass + t.cipher.setAutoPadding() + t.end() + }) + + t.test('should return `this`', function (t) { + t.cipher._setAutoPadding = noop + t.equal(t.cipher.setAutoPadding(), t.cipher) + t.end() + }) + + t.end() +}) + +test('CipherBase#_setAutoPadding', function (t) { + beforeEach(t, createCipher) + + t.test('is not implemented', function (t) { + t.throws(function () { + t.cipher._setAutoPadding() + }, /^Error: _setAutoPadding is not implemented$/) + t.end() + }) + + t.end() +}) + +test('CipherBase#getAuthTag', function (t) { + beforeEach(t, createCipher, adjustCipher, createDeciper) + + function adjustCipher (t) { + t.cipher._authTag = Buffer.from(utf8buf) + t.cipher._finalized = true } - Cipher.prototype._final = function () { - // noop + + var errRegExp = /^Error: Attempting to get auth tag in unsupported state$/ + + t.test('should throw error for decipher', function (t) { + t.throws(function () { + t.decipher.getAuthTag() + }, errRegExp) + t.end() + }) + + t.test('should throw error if auth tag is not defined', function (t) { + t.cipher._authTag = null + t.throws(function () { + t.decipher.getAuthTag() + }, errRegExp) + t.end() + }) + + t.test('should throw error if called before CipherBase#final', function (t) { + t.cipher._finalized = false + t.throws(function () { + t.decipher.getAuthTag() + }, errRegExp) + t.end() + }) + + t.test('should return auth tag', function (t) { + var tagbuf = t.cipher.getAuthTag() + t.same(tagbuf, utf8buf) + t.end() + }) + + t.end() +}) + +// TODO +test('CipherBase#setAuthTag', function (t) { + beforeEach(t, createCipher, createDeciper, adjustCipherDecipher) + + function adjustCipherDecipher (t) { + t.cipher._isAuthenticatedMode = function () { return true } + t.decipher._isAuthenticatedMode = function () { return true } } - t.test('mix and match encoding', function (t) { - t.plan(2) - var cipher = new Cipher() - cipher.update('foo', 'utf8', 'utf8') + var errRegExp = /^Error: Attempting to set auth tag in unsupported state$/ + + t.test('auth tag should be buffer', function (t) { t.throws(function () { - cipher.update('foo', 'utf8', 'base64') - }) - cipher = new Cipher() - cipher.update('foo', 'utf8', 'base64') - t.doesNotThrow(function () { - cipher.update('foo', 'utf8') - cipher.final('base64') - }) + t.decipher.setAuthTag(null) + }, /^TypeError: Auth tag must be a buffer$/) + t.end() + }) + + t.test('should throw error if is not authefication mode', function (t) { + t.decipher._isAuthenticatedMode = function () { return false } + t.throws(function () { + t.decipher.setAuthTag(Buffer.allocUnsafe(0)) + }, errRegExp) + t.end() + }) + + t.test('should throw error for cipher', function (t) { + t.throws(function () { + t.cipher.setAuthTag(Buffer.allocUnsafe(0)) + }, errRegExp) + t.end() + }) + + t.test('should throw error if called after CipherBase#fianl', function (t) { + t.decipher._final = noop + t.decipher.final() + t.throws(function () { + t.decipher.setAuthTag(Buffer.allocUnsafe(0)) + }, errRegExp) + t.end() + }) + + t.test('should set _authTag', function (t) { + t.decipher.setAuthTag(utf8buf) + t.same(t.decipher._authTag, utf8buf) + t.end() + }) + + t.test('should return `this`', function (t) { + t.equal(t.decipher.setAuthTag(Buffer.allocUnsafe(0)), t.decipher) + t.end() + }) + + t.end() +}) + +test('CipherBase#setAAD', function (t) { + beforeEach(t, createCipher) + + t.test('authefication data should be buffer', function (t) { + t.throws(function () { + t.cipher.setAAD(null) + }, /^TypeError: AAD must be a buffer$/) + t.end() + }) + + t.test('should throw error after CipherBase#final', function (t) { + t.cipher._isAuthenticatedMode = function () { return true } + t.cipher._final = noop + t.cipher.final() + t.throws(function () { + t.cipher.setAAD(Buffer.allocUnsafe(0)) + }, /^Error: Attempting to set AAD in unsupported state$/) + t.end() + }) + + t.test('should throw error if not authefication mode', function (t) { + t.cipher._isAuthenticatedMode = function () { return false } + t.throws(function () { + t.cipher.setAAD(Buffer.allocUnsafe(0)) + }, /^Error: Attempting to set AAD in unsupported state$/) + t.end() }) - t.test('handle long uft8 plaintexts', function (t) { + + t.test('should call CipherBase#_setAAD', function (t) { t.plan(1) - var txt = 'ふっかつ あきる すぶり はやい つける まゆげ たんさん みんぞく ねほりはほり せまい たいまつばな ひはん' + t.cipher._isAuthenticatedMode = function () { return true } + t.cipher._setAAD = t.pass + t.cipher.setAAD(Buffer.allocUnsafe(0)) + t.end() + }) + + t.test('should return `this`', function (t) { + t.cipher._isAuthenticatedMode = function () { return true } + t.cipher._setAAD = noop + t.equal(t.cipher.setAAD(Buffer.allocUnsafe(0)), t.cipher) + t.end() + }) + + t.end() +}) - var cipher = new Cipher() - var decipher = new Cipher() - var enc = decipher.update(cipher.update(txt, 'utf8', 'base64'), 'base64', 'utf8') - enc += decipher.update(cipher.final('base64'), 'base64', 'utf8') - enc += decipher.final('utf8') +test('CipherBase#_setAAD', function (t) { + beforeEach(t, createCipher) - t.equals(txt, enc) + t.test('is not implemented', function (t) { + t.throws(function () { + t.cipher._setAAD() + }, /^Error: _setAAD is not implemented$/) + t.end() }) + + t.end() }) From f1862e8fb6323760ba5d1659cbcdb8289d16e000 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 2 Nov 2016 15:07:03 +0300 Subject: [PATCH 6/7] Update CipherBase --- index.js | 190 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 64 deletions(-) diff --git a/index.js b/index.js index 1a661d6..8e43bff 100644 --- a/index.js +++ b/index.js @@ -1,90 +1,152 @@ +'use strict' var Transform = require('stream').Transform var inherits = require('inherits') var StringDecoder = require('string_decoder').StringDecoder -module.exports = CipherBase -inherits(CipherBase, Transform) -function CipherBase (hashMode) { - Transform.call(this) - this.hashMode = typeof hashMode === 'string' - if (this.hashMode) { - this[hashMode] = this._finalOrDigest - } else { - this.final = this._finalOrDigest - } - this._decoder = null - this._encoding = null -} -CipherBase.prototype.update = function (data, inputEnc, outputEnc) { - if (typeof data === 'string') { - data = new Buffer(data, inputEnc) - } - var outData = this._update(data) - if (this.hashMode) { - return this - } - if (outputEnc) { - outData = this._toString(outData, outputEnc) + +var K_CIPHER = 0 +var K_DECIPHER = 1 + +function throwIfNotStringOrBuffer (val, prefix) { + if (!Buffer.isBuffer(val) && typeof val !== 'string') { + throw new TypeError(prefix + ' must be a string or a buffer') } - return outData } -CipherBase.prototype.setAutoPadding = function () {} +function throwIfNotBuffer (val, prefix) { + if (!Buffer.isBuffer(val)) throw new TypeError(prefix + ' must be a buffer') +} -CipherBase.prototype.getAuthTag = function () { - throw new Error('trying to get auth tag in unsupported state') +function getDecoder (decoder, encoding) { + decoder = decoder || new StringDecoder(encoding) + if (decoder.encoding !== encoding) throw new Error('Cannot change encoding') + return decoder } -CipherBase.prototype.setAuthTag = function () { - throw new Error('trying to set auth tag in unsupported state') +function CipherBase (chipher) { + Transform.call(this) + + this._kind = chipher ? K_CIPHER : K_DECIPHER + this._authTag = null + this._decoder = null + this._finalized = false } -CipherBase.prototype.setAAD = function () { - throw new Error('trying to set aad in unsupported state') +inherits(CipherBase, Transform) + +CipherBase.prototype._isAuthenticatedMode = function () { + throw new Error('_isAuthenticatedMode is not implemented') } -CipherBase.prototype._transform = function (data, _, next) { - var err +CipherBase.prototype._transform = function (chunk, encoding, callback) { + var error = null try { - if (this.hashMode) { - this._update(data) - } else { - this.push(this._update(data)) - } - } catch (e) { - err = e - } finally { - next(err) + this.update(chunk, encoding) + } catch (err) { + error = err } + + callback(error) } -CipherBase.prototype._flush = function (done) { - var err + +CipherBase.prototype._flush = function (callback) { + var error = null try { - this.push(this._final()) - } catch (e) { - err = e - } finally { - done(err) + this.push(this.final()) + } catch (err) { + error = err } + + callback(error) } -CipherBase.prototype._finalOrDigest = function (outputEnc) { - var outData = this._final() || new Buffer('') - if (outputEnc) { - outData = this._toString(outData, outputEnc, true) + +CipherBase.prototype.update = function (data, inputEncoding, outputEncoding) { + throwIfNotStringOrBuffer(data, 'Cipher data') + if (this._finalized) throw new Error('Trying to add data in unsupported state') + + if (!Buffer.isBuffer(data)) data = Buffer.from(data, inputEncoding) + + data = this._update(data) + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding) + data = this._decoder.write(data) } - return outData + return data +} + +CipherBase.prototype._update = function () { + throw new Error('_update is not implemented') } -CipherBase.prototype._toString = function (value, enc, fin) { - if (!this._decoder) { - this._decoder = new StringDecoder(enc) - this._encoding = enc +CipherBase.prototype.final = function (outputEncoding) { + if (this._finalized) { + var msg = this._isAuthenticatedMode() + ? 'Unsupported state or unable to authenticate data' + : 'Unsupported state' + throw new Error(msg) } - if (this._encoding !== enc) { - throw new Error('can\'t switch encodings') + this._finalized = true + + var data = this._final() + if (outputEncoding && outputEncoding !== 'buffer') { + this._decoder = getDecoder(this._decoder, outputEncoding) + data = this._decoder.end(data) } - var out = this._decoder.write(value) - if (fin) { - out += this._decoder.end() + + if (this._kind === K_DECIPHER && this._isAuthenticatedMode() && this._authTag !== null) { + this._authTag.fill(0) + this._authTag = null } - return out + + return data +} + +CipherBase.prototype._final = function (outputEncoding) { + throw new Error('_final is not implemented') +} + +CipherBase.prototype.setAutoPadding = function (ap) { + if (this._finalized) { + throw new Error('Attempting to set auto padding in unsupported state') + } + + this._setAutoPadding(ap) + return this +} + +CipherBase.prototype._setAutoPadding = function (ap) { + throw new Error('_setAutoPadding is not implemented') +} + +CipherBase.prototype.getAuthTag = function () { + if (this._kind !== K_CIPHER || this._authTag === null || !this._finalized) { + throw new Error('Attempting to get auth tag in unsupported state') + } + + return Buffer.from(this._authTag) } + +CipherBase.prototype.setAuthTag = function (tagbuf) { + throwIfNotBuffer(tagbuf, 'Auth tag') + if (!this._isAuthenticatedMode() || this._kind !== K_DECIPHER || this._finalized) { + throw new Error('Attempting to set auth tag in unsupported state') + } + + this._authTag = Buffer.from(tagbuf) + return this +} + +CipherBase.prototype.setAAD = function (aadbuf) { + throwIfNotBuffer(aadbuf, 'AAD') + if (!this._isAuthenticatedMode() || this._finalized) { + throw new Error('Attempting to set AAD in unsupported state') + } + + this._setAAD(aadbuf) + return this +} + +CipherBase.prototype._setAAD = function (aadbuf) { + throw new Error('_setAAD is not implemented') +} + +module.exports = CipherBase From edb41a763eb5fb995eab9574c72c1b260c11acc2 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Wed, 2 Nov 2016 15:30:10 +0300 Subject: [PATCH 7/7] Update README.md --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index db9a781..7e9cd5f 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,53 @@ -cipher-base -=== +# cipher-base -[![Build Status](https://travis-ci.org/crypto-browserify/cipher-base.svg)](https://travis-ci.org/crypto-browserify/cipher-base) +[![NPM Package](https://img.shields.io/npm/v/cipher-base.svg?style=flat-square)](https://www.npmjs.org/package/cipher-base) +[![Build Status](https://img.shields.io/travis/crypto-browserify/cipher-base.svg?branch=master&style=flat-square)](https://travis-ci.org/crypto-browserify/cipher-base) -Abstract base class to inherit from if you want to create streams implementing -the same api as node crypto streams. +[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) -Requires you to implement 2 methods `_final` and `_update`. `_update` takes a -buffer and should return a buffer, `_final` takes no arguments and should return -a buffer. +Abstract base class to inherit from if you want to create streams implementing the same API as node crypto [Cipher][1] or [Decipher][2] (for [Hash][3] check [crypto-browserify/hash-base][4]). +## Example -The constructor takes one argument and that is a string which if present switches -it into hash mode, i.e. the object you get from crypto.createHash or -crypto.createSign, this switches the name of the final method to be the string -you passed instead of `final` and returns `this` from update. +```js +const CipherBase = require('cipher-base') +const inherits = require('inherits') + +// our cipher will apply XOR 0x42 to every byte +function MyCipher () { + CipherBase.call(this, true) // for Deciper you need pass `false` +} + +inherits(MyCipher, CipherBase) + +MyCipher.prototype._isAuthenticatedMode = function () { + return false +} + +MyCipher.prototype._setAutoPadding = function (ap) {} +MyCipher.prototype._setAAD = function (aadbuf) {} + +MyCipher.prototype._update = function (data) { + const result = Buffer.allocUnsafe(data.length) + for (let i = 0; i < data.length; ++i) result[i] = data[i] ^ 0x42 + return result +} + +MyCipher.prototype._final = function () { + return Buffer.allocUnsafe(0) +} + +const data = Buffer.from([ 0x00, 0x42 ]) +const cipher = new MyCipher() +console.log(Buffer.concat([cipher.update(data), cipher.final()])) +// => +``` + +## LICENSE + +MIT + +[1]: https://nodejs.org/api/crypto.html#crypto_class_cipher +[2]: https://nodejs.org/api/crypto.html#crypto_class_decipher +[3]: https://nodejs.org/api/crypto.html#crypto_class_hash +[4]: https://github.com/crypto-browserify/hash-base