From 317ed37bbca4cb029fdeea52271d35e37440f3d3 Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Wed, 23 Jan 2019 13:05:49 +0000 Subject: [PATCH] [FABN-909] Move hash classes into fabric-common fabric-client contains a file called hash.js that exports a set of things: - classes named hash_* that implement the Hash interface - functions named SHA_* that take data in and return a hash - shake_256 The hash_* classes are only used in a couple of places outside of this file, and they would be better off using the SHA_* functions instead. hash_sha3_256 and hash_sha3_384 are not used anywhere, and cannot be used by external code as they are not listed in the CryptoAlgorithms constants, so they can be removed safely. The SHA3_* functions are used and must stay. shake_256 is not used anywhere, and again cannot be used by external code as it is not listed in the CryptoAlgorithms constants, so remove that as well. Also, it has a TODO on it! TL;DR - move hash_sha2_256 and hash_sha2_384 into fabric-common, export the SHA_* functions via a HashPrimitives object, and use that everywhere. Also had to add an encoding flag to the SHA2_* functions to allow them to return Buffer objects as required in several places (instead of hex strings). Change-Id: I7fd2730d73109412cd9ddab4c9e32fec34b47321 Signed-off-by: Simon Stone --- build/tasks/ca.js | 1 - fabric-client/lib/Remote.js | 5 +- fabric-client/lib/TransactionID.js | 5 +- fabric-client/lib/hash.js | 120 -------- .../lib/impl/CryptoSuite_ECDSA_AES.js | 9 +- fabric-client/lib/impl/bccsp_pkcs11.js | 12 +- fabric-client/lib/impl/ecdsa/key.js | 5 +- fabric-client/test/Remote.js | 18 +- fabric-client/test/hash.js | 274 ------------------ fabric-common/index.js | 4 +- fabric-common/lib/Hash.js | 1 + fabric-common/lib/HashPrimitives.js | 32 ++ fabric-common/lib/hash/hash_sha2_256.js | 44 +++ fabric-common/lib/hash/hash_sha2_384.js | 44 +++ fabric-common/test/HashPrimitives.js | 105 +++++++ fabric-common/test/hash/hash_sha2_256.js | 72 +++++ fabric-common/test/hash/hash_sha2_384.js | 72 +++++ test/integration/signTransactionOffline.js | 4 +- 18 files changed, 391 insertions(+), 436 deletions(-) delete mode 100644 fabric-client/lib/hash.js delete mode 100644 fabric-client/test/hash.js create mode 100644 fabric-common/lib/HashPrimitives.js create mode 100644 fabric-common/lib/hash/hash_sha2_256.js create mode 100644 fabric-common/lib/hash/hash_sha2_384.js create mode 100644 fabric-common/test/HashPrimitives.js create mode 100644 fabric-common/test/hash/hash_sha2_256.js create mode 100644 fabric-common/test/hash/hash_sha2_384.js diff --git a/build/tasks/ca.js b/build/tasks/ca.js index d2be99525b..e83db7b899 100644 --- a/build/tasks/ca.js +++ b/build/tasks/ca.js @@ -9,7 +9,6 @@ const gulp = require('gulp'); const debug = require('gulp-debug'); const DEPS = [ - 'fabric-client/lib/hash.js', 'fabric-client/lib/utils.js', 'fabric-client/lib/BaseClient.js', 'fabric-client/lib/Remote.js', diff --git a/fabric-client/lib/Remote.js b/fabric-client/lib/Remote.js index 1e669bfbaf..9df8f32665 100644 --- a/fabric-client/lib/Remote.js +++ b/fabric-client/lib/Remote.js @@ -20,7 +20,7 @@ const urlParser = require('url'); const utils = require('./utils.js'); const logger = utils.getLogger('Remote.js'); -const {hash_sha2_256} = require('./hash'); +const {HashPrimitives} = require('fabric-common'); const MAX_SEND = 'grpc.max_send_message_length'; const MAX_RECEIVE = 'grpc.max_receive_message_length'; const MAX_SEND_V10 = 'grpc-max-send-message-length'; @@ -197,8 +197,7 @@ class Remote { getClientCertHash() { if (this.clientCert) { const der_cert = utils.pemToDER(this.clientCert); - const hash = new hash_sha2_256(); - return hash.reset().update(der_cert).finalize(); + return HashPrimitives.SHA2_256(der_cert, null /* We need a Buffer */); } else { return null; } diff --git a/fabric-client/lib/TransactionID.js b/fabric-client/lib/TransactionID.js index f8b15424fd..8c8b9bbab1 100644 --- a/fabric-client/lib/TransactionID.js +++ b/fabric-client/lib/TransactionID.js @@ -10,8 +10,7 @@ const utils = require('./utils.js'); const logger = utils.getLogger('TransactionID.js'); const User = require('./User.js'); -const hashPrimitives = require('./hash.js'); - +const {HashPrimitives} = require('fabric-common'); /** * The class representing the transaction identifier. Provides for @@ -44,7 +43,7 @@ class TransactionID { this._nonce = utils.getNonce(); // nonce is in bytes const creator_bytes = signer.serialize();// same as signatureHeader.Creator const trans_bytes = Buffer.concat([this._nonce, creator_bytes]); - const trans_hash = hashPrimitives.SHA2_256(trans_bytes); + const trans_hash = HashPrimitives.SHA2_256(trans_bytes); this._transaction_id = Buffer.from(trans_hash).toString(); logger.debug('const - transaction_id %s', this._transaction_id); diff --git a/fabric-client/lib/hash.js b/fabric-client/lib/hash.js deleted file mode 100644 index cfc0140d49..0000000000 --- a/fabric-client/lib/hash.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Implement hash primitives. - */ -const jsSHA3 = require('js-sha3'); -const {sha3_256, sha3_384, shake_256} = jsSHA3; -const crypto = require('crypto'); -const {Hash} = require('fabric-common'); - -class hash_sha2_256 extends Hash { - constructor() { - super(512); - } - - hash(data) { - return this.reset().update(data).finalize('hex'); - } - - reset() { - this._hash = crypto.createHash('sha256'); - return super.reset(); - } - - finalize(encoding) { - const hash = this._hash.digest(encoding); - this.reset(); - return hash; - } -} - -class hash_sha2_384 extends Hash { - constructor() { - super(1024); - } - - hash(data) { - return this.reset().update(data).finalize('hex'); - } - - reset() { - this._hash = crypto.createHash('sha384'); - return super.reset(); - } - - finalize(encoding) { - const hash = this._hash.digest(encoding); - this.reset(); - return hash; - } -} - -class hash_sha3_256 extends Hash { - static hashSimple(data) { - return sha3_256(data); - } - - constructor() { - super(1088); - } - - reset() { - this._hash = sha3_256.create(); - return super.reset(); - } - - finalize() { - const hash = this._hash.hex(); - this.reset(); - return hash; - } - -} - -class hash_sha3_384 extends Hash { - static hashSimple(data) { - return sha3_384(data); - } - - constructor() { - super(832); - } - - reset() { - this._hash = sha3_384.create(); - return super.reset(); - } - - finalize() { - const hash = this._hash.hex(); - this.reset(); - return hash; - } -} - -exports.hash_sha3_256 = hash_sha3_256; -exports.hash_sha3_384 = hash_sha3_384; -exports.hash_sha2_256 = hash_sha2_256; -exports.hash_sha2_384 = hash_sha2_384; -exports.SHA2_256 = (data) => { - return (new hash_sha2_256()).hash(data); -}; -exports.SHA3_256 = sha3_256; -exports.SHA2_384 = (data) => { - return (new hash_sha2_384()).hash(data); -}; -exports.SHA3_384 = sha3_384; -exports.shake_256 = shake_256;// TODO diff --git a/fabric-client/lib/impl/CryptoSuite_ECDSA_AES.js b/fabric-client/lib/impl/CryptoSuite_ECDSA_AES.js index dfcfc1ac8f..b6045550a7 100755 --- a/fabric-client/lib/impl/CryptoSuite_ECDSA_AES.js +++ b/fabric-client/lib/impl/CryptoSuite_ECDSA_AES.js @@ -8,16 +8,13 @@ 'use strict'; // requires -const {CryptoAlgorithms, CryptoSuite} = require('fabric-common'); - +const {CryptoAlgorithms, CryptoSuite, HashPrimitives} = require('fabric-common'); const elliptic = require('elliptic'); const EC = elliptic.ec; const jsrsa = require('jsrsasign'); const {KEYUTIL} = jsrsa; const util = require('util'); const Signature = require('elliptic/lib/elliptic/ec/signature.js'); - -const hashPrimitives = require('../hash.js'); const utils = require('../utils'); const ECDSAKey = require('./ecdsa/key.js'); @@ -56,7 +53,7 @@ class CryptoSuite_ECDSA_AES extends CryptoSuite { } hashAlgo = hashAlgo.toUpperCase(); const hashPair = `${hashAlgo}_${keySize}`; - if (!CryptoAlgorithms[hashPair] || !hashPrimitives[hashPair]) { + if (!CryptoAlgorithms[hashPair] || !HashPrimitives[hashPair]) { throw Error(util.format('Unsupported hash algorithm and key size pair: %s', hashPair)); } super(); @@ -72,7 +69,7 @@ class CryptoSuite_ECDSA_AES extends CryptoSuite { logger.debug('Hash algorithm: %s, hash output size: %s', this._hashAlgo, this._keySize); - this._hashFunction = hashPrimitives[hashPair]; + this._hashFunction = HashPrimitives[hashPair]; this._hashOutputSize = this._keySize / 8; diff --git a/fabric-client/lib/impl/bccsp_pkcs11.js b/fabric-client/lib/impl/bccsp_pkcs11.js index 60948280be..c36cea1778 100644 --- a/fabric-client/lib/impl/bccsp_pkcs11.js +++ b/fabric-client/lib/impl/bccsp_pkcs11.js @@ -7,7 +7,7 @@ 'use strict'; -const {CryptoAlgorithms, CryptoSuite} = require('fabric-common'); +const {CryptoAlgorithms, CryptoSuite, HashPrimitives} = require('fabric-common'); const utils = require('../utils'); const aesKey = require('./aes/pkcs11_key.js'); const ecdsaKey = require('./ecdsa/pkcs11_key.js'); @@ -25,7 +25,6 @@ const callsite = require('callsite'); const pkcs11js = require('pkcs11js'); const util = require('util'); const ECDSAKey = require('./ecdsa/key.js'); -const hashPrimitives = require('../hash.js'); const logger = utils.getLogger('crypto_pkcs11'); @@ -206,7 +205,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite { } hashAlgo = hashAlgo.toUpperCase(); const hashPair = `${hashAlgo}_${keySize}`; - if (!CryptoAlgorithms[hashPair] || !hashPrimitives[hashPair]) { + if (!CryptoAlgorithms[hashPair] || !HashPrimitives[hashPair]) { throw Error(util.format('Unsupported hash algorithm and key size pair: %s', hashPair)); } @@ -219,7 +218,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite { this._hashAlgo = hashAlgo; - this._hashFunction = hashPrimitives[hashPair]; + this._hashFunction = HashPrimitives[hashPair]; /* * Load native PKCS11 library, open PKCS11 session and login. @@ -251,8 +250,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite { * sha256 of tod as SKI. */ _ski() { - const hash = new hashPrimitives.hash_sha2_256(); - return hash.reset().update(this._tod()).finalize(); + return HashPrimitives.SHA2_256(this._tod(), null /* We need a Buffer */); } /* @@ -475,7 +473,7 @@ class CryptoSuite_PKCS11 extends CryptoSuite { /* * Set CKA_ID of public and private key to be SKI. */ - const ski = Buffer.from(hashPrimitives.SHA2_256(ecpt), 'hex'); + const ski = HashPrimitives.SHA2_256(ecpt, null /* We want a Buffer */); this._pkcs11SetAttributeValue( pkcs11, pkcs11Session, handles.publicKey, [{type: pkcs11js.CKA_ID, value: ski}, {type: pkcs11js.CKA_LABEL, value: ski.toString('hex')}]); diff --git a/fabric-client/lib/impl/ecdsa/key.js b/fabric-client/lib/impl/ecdsa/key.js index a9cf9f0b20..6ac76550fe 100644 --- a/fabric-client/lib/impl/ecdsa/key.js +++ b/fabric-client/lib/impl/ecdsa/key.js @@ -7,14 +7,13 @@ 'use strict'; -const Hash = require('../../hash.js'); const utils = require('../../utils.js'); const jsrsa = require('jsrsasign'); const asn1 = jsrsa.asn1; const KEYUTIL = jsrsa.KEYUTIL; const ECDSA = jsrsa.ECDSA; const jws = jsrsa.jws; -const {Key} = require('fabric-common'); +const {HashPrimitives, Key} = require('fabric-common'); const logger = utils.getLogger('ecdsa/key.js'); /** @@ -76,7 +75,7 @@ module.exports = class ECDSA_KEY extends Key { } // always use SHA256 regardless of the key size in effect - return Hash.SHA2_256(buff); + return HashPrimitives.SHA2_256(buff); } isSymmetric() { diff --git a/fabric-client/test/Remote.js b/fabric-client/test/Remote.js index 97a8cbc673..c84ca3835d 100644 --- a/fabric-client/test/Remote.js +++ b/fabric-client/test/Remote.js @@ -270,31 +270,19 @@ describe('Remote', () => { describe('#getClientCertHash', () => { let remote; let hashStub; - let updateStub; - let finalizeStub; beforeEach(() => { remote = new Remote('grpc://someurl'); - updateStub = sandbox.stub(); - finalizeStub = sandbox.stub(); - hashStub = sandbox.stub().returns({ - reset: () => { - return { - update: updateStub.returns({finalize: finalizeStub}) - }; - } - }); - revert.push(Remote.__set__('hash_sha2_256', hashStub)); + hashStub = sandbox.stub().returns('cert-hash'); + revert.push(Remote.__set__('HashPrimitives.SHA2_256', hashStub)); }); it('should return the hashed client certificate', () => { FakeUtils.pemToDER.returns('der-cert'); - finalizeStub.returns('cert-hash'); remote.clientCert = 'clientCert'; const certHash = remote.getClientCertHash(); sinon.assert.calledWith(FakeUtils.pemToDER, remote.clientCert); sinon.assert.called(hashStub); - sinon.assert.calledWith(updateStub, 'der-cert'); - sinon.assert.called(finalizeStub); + sinon.assert.calledWith(hashStub, 'der-cert'); certHash.should.equal('cert-hash'); }); diff --git a/fabric-client/test/hash.js b/fabric-client/test/hash.js deleted file mode 100644 index 8ee85471d4..0000000000 --- a/fabric-client/test/hash.js +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict'; - -const rewire = require('rewire'); -const Hash = rewire('../lib/hash'); -const {hash_sha2_256, hash_sha2_384, hash_sha3_256, hash_sha3_384} = Hash; -const sinon = require('sinon'); - -describe('hash_sha2_256', () => { - let sandbox; - let revert; - let hash; - - beforeEach(() => { - revert = []; - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - if (revert.length) { - revert.forEach(Function.prototype.call, Function.prototype.call); - } - sandbox.restore(); - }); - - describe('#hash', () => { - it('should call the reset reset.update.finalize and return the correct value', () => { - hash = new hash_sha2_256(); - const finalizeStub = sandbox.stub().returns('finalize'); - const updateStub = sandbox.stub().returns({finalize: finalizeStub}); - const resetStub = sandbox.stub(hash, 'reset').returns({update: updateStub}); - hash.hash('data').should.equal('finalize'); - sinon.assert.called(resetStub); - sinon.assert.calledWith(updateStub, 'data'); - sinon.assert.calledWith(finalizeStub, 'hex'); - }); - }); - - describe('#reset', () => { - it('should call crypto.createHash', () => { - const cryptoStub = {createHash: sandbox.stub()}; - revert.push(Hash.__set__('crypto', cryptoStub)); - hash = new hash_sha2_256(); - hash.reset(); - sinon.assert.calledWith(cryptoStub.createHash, 'sha256'); - }); - }); - - describe('#finalize', () => { - it('should call _hash.digest, reset and reurn the hash', () => { - hash = new hash_sha2_256(); - const digestStub = sandbox.stub().returns('hash'); - hash._hash = {digest: digestStub}; - const resetStub = sandbox.stub(hash, 'reset'); - hash.finalize('encoding').should.equal('hash'); - sinon.assert.calledWith(digestStub, 'encoding'); - sinon.assert.called(resetStub); - }); - }); -}); - -describe('hash_sha2_384', () => { - let sandbox; - let revert; - let hash; - - beforeEach(() => { - revert = []; - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - if (revert.length) { - revert.forEach(Function.prototype.call, Function.prototype.call); - } - sandbox.restore(); - }); - - describe('#hash', () => { - it('should call the reset reset.update.finalize and return the correct value', () => { - hash = new hash_sha2_384(); - const finalizeStub = sandbox.stub().returns('finalize'); - const updateStub = sandbox.stub().returns({finalize: finalizeStub}); - const resetStub = sandbox.stub(hash, 'reset').returns({update: updateStub}); - hash.hash('data').should.equal('finalize'); - sinon.assert.called(resetStub); - sinon.assert.calledWith(updateStub, 'data'); - sinon.assert.calledWith(finalizeStub, 'hex'); - }); - }); - - describe('#reset', () => { - it('should call crypto.createHash', () => { - const cryptoStub = {createHash: sandbox.stub()}; - revert.push(Hash.__set__('crypto', cryptoStub)); - hash = new hash_sha2_384(); - hash.reset(); - sinon.assert.calledWith(cryptoStub.createHash, 'sha384'); - }); - }); - - describe('#finalize', () => { - it('should call _hash.digest, reset and reurn the hash', () => { - hash = new hash_sha2_384(); - const digestStub = sandbox.stub().returns('hash'); - hash._hash = {digest: digestStub}; - const resetStub = sandbox.stub(hash, 'reset'); - hash.finalize('encoding').should.equal('hash'); - sinon.assert.calledWith(digestStub, 'encoding'); - sinon.assert.called(resetStub); - }); - }); -}); - -describe('hash_sha3_256', () => { - let sandbox; - let revert; - let hash; - - beforeEach(() => { - revert = []; - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - if (revert.length) { - revert.forEach(Function.prototype.call, Function.prototype.call); - } - sandbox.restore(); - }); - - describe('hashSimple', () => { - it('should create an instance of sha3_256', () => { - const mockSha3256 = sandbox.stub(); - revert.push(Hash.__set__('sha3_256', mockSha3256)); - hash_sha3_256.hashSimple('data'); - sinon.assert.calledWith(mockSha3256, 'data'); - }); - }); - - describe('#reset', () => { - it('should call sha3_256.create', () => { - const sha3_256Stub = {create: sandbox.stub()}; - revert.push(Hash.__set__('sha3_256', sha3_256Stub)); - hash = new hash_sha3_256(); - hash.reset(); - sinon.assert.called(sha3_256Stub.create); - }); - }); - - describe('#finalize', () => { - it('should call _hash.hex, reset and reurn the hash', () => { - hash = new hash_sha3_256(); - const hexStub = sandbox.stub().returns('hash'); - hash._hash = {hex: hexStub}; - const resetStub = sandbox.stub(hash, 'reset'); - hash.finalize().should.equal('hash'); - sinon.assert.called(hexStub); - sinon.assert.called(resetStub); - }); - }); -}); - -describe('hash_sha3_384', () => { - let sandbox; - let revert; - let hash; - - beforeEach(() => { - revert = []; - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - if (revert.length) { - revert.forEach(Function.prototype.call, Function.prototype.call); - } - sandbox.restore(); - }); - - describe('hashSimple', () => { - it('should create an instance of hash_sha3_384', () => { - const mockSha3384 = sandbox.stub(); - revert.push(Hash.__set__('sha3_384', mockSha3384)); - hash_sha3_384.hashSimple('data'); - sinon.assert.calledWith(mockSha3384, 'data'); - }); - }); - - describe('#reset', () => { - it('should call sha3_384.create', () => { - const sha3_384Stub = {create: sandbox.stub()}; - revert.push(Hash.__set__('sha3_384', sha3_384Stub)); - hash = new hash_sha3_384(); - hash.reset(); - sinon.assert.called(sha3_384Stub.create); - }); - }); - - describe('#finalize', () => { - it('should call _hash.hex, reset and reurn the hash', () => { - hash = new hash_sha3_384(); - const hexStub = sandbox.stub().returns('hash'); - hash._hash = {hex: hexStub}; - const resetStub = sandbox.stub(hash, 'reset'); - hash.finalize().should.equal('hash'); - sinon.assert.called(hexStub); - sinon.assert.called(resetStub); - }); - }); -}); - -describe('SHA2_256', () => { - let sandbox; - let revert; - - beforeEach(() => { - revert = []; - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - if (revert.length) { - revert.forEach(Function.prototype.call, Function.prototype.call); - } - sandbox.restore(); - }); - it('should call hash and return an insatnce of hash_sha2_256', () => { - const mockHashFunction = sandbox.stub().returns('hash'); - const mockHash = sandbox.stub().returns({hash: mockHashFunction}); - revert.push(Hash.__set__('hash_sha2_256', mockHash)); - Hash.SHA2_256('data').should.equal('hash'); - sinon.assert.called(mockHash); - sinon.assert.calledWith(mockHashFunction, 'data'); - }); -}); - -describe('SHA2_384', () => { - let sandbox; - let revert; - - beforeEach(() => { - revert = []; - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - if (revert.length) { - revert.forEach(Function.prototype.call, Function.prototype.call); - } - sandbox.restore(); - }); - it('should call hash and return an insatnce of hash_sha2_384', () => { - const mockHashFunction = sandbox.stub().returns('hash'); - const mockHash = sandbox.stub().returns({hash: mockHashFunction}); - revert.push(Hash.__set__('hash_sha2_384', mockHash)); - Hash.SHA2_384('data').should.equal('hash'); - sinon.assert.called(mockHash); - sinon.assert.calledWith(mockHashFunction, 'data'); - }); -}); diff --git a/fabric-common/index.js b/fabric-common/index.js index a1a3a6572e..32f7102263 100644 --- a/fabric-common/index.js +++ b/fabric-common/index.js @@ -7,7 +7,7 @@ const Config = require('./lib/Config'); const CryptoAlgorithms = require('./lib/CryptoAlgorithms'); const CryptoSuite = require('./lib/CryptoSuite'); -const Hash = require('./lib/Hash'); +const HashPrimitives = require('./lib/HashPrimitives'); const Key = require('./lib/Key'); const KeyValueStore = require('./lib/KeyValueStore'); @@ -15,7 +15,7 @@ module.exports = { Config, CryptoAlgorithms, CryptoSuite, - Hash, + HashPrimitives, Key, KeyValueStore }; diff --git a/fabric-common/lib/Hash.js b/fabric-common/lib/Hash.js index 0afa6582af..b0b26a55d5 100644 --- a/fabric-common/lib/Hash.js +++ b/fabric-common/lib/Hash.js @@ -16,6 +16,7 @@ /** * Base class for hash primitives. + * @abstract * @type {Hash} */ class Hash { diff --git a/fabric-common/lib/HashPrimitives.js b/fabric-common/lib/HashPrimitives.js new file mode 100644 index 0000000000..4dd61dfd64 --- /dev/null +++ b/fabric-common/lib/HashPrimitives.js @@ -0,0 +1,32 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const hash_sha2_256 = require('./hash/hash_sha2_256'); +const hash_sha2_384 = require('./hash/hash_sha2_384'); +const {sha3_256, sha3_384} = require('js-sha3'); + +const HashPrimitives = { + SHA2_256: (data, encoding = 'hex') => { + return (new hash_sha2_256()).hash(data, encoding); + }, + SHA2_384: (data, encoding = 'hex') => { + return (new hash_sha2_384()).hash(data, encoding); + }, + SHA3_256: sha3_256, + SHA3_384: sha3_384 +}; + +module.exports = HashPrimitives; \ No newline at end of file diff --git a/fabric-common/lib/hash/hash_sha2_256.js b/fabric-common/lib/hash/hash_sha2_256.js new file mode 100644 index 0000000000..ac59a4b3e2 --- /dev/null +++ b/fabric-common/lib/hash/hash_sha2_256.js @@ -0,0 +1,44 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Implement hash primitives. + */ +const crypto = require('crypto'); +const Hash = require('../Hash'); + +class hash_sha2_256 extends Hash { + + constructor() { + super(512); + } + + hash(data, encoding = 'hex') { + return this.reset().update(data).finalize(encoding); + } + + reset() { + this._hash = crypto.createHash('sha256'); + return super.reset(); + } + + finalize(encoding) { + const hash = this._hash.digest(encoding); + this.reset(); + return hash; + } + +} + +module.exports = hash_sha2_256; \ No newline at end of file diff --git a/fabric-common/lib/hash/hash_sha2_384.js b/fabric-common/lib/hash/hash_sha2_384.js new file mode 100644 index 0000000000..7774f798e6 --- /dev/null +++ b/fabric-common/lib/hash/hash_sha2_384.js @@ -0,0 +1,44 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Implement hash primitives. + */ +const crypto = require('crypto'); +const Hash = require('../Hash'); + +class hash_sha2_384 extends Hash { + + constructor() { + super(1024); + } + + hash(data, encoding = 'hex') { + return this.reset().update(data).finalize(encoding); + } + + reset() { + this._hash = crypto.createHash('sha384'); + return super.reset(); + } + + finalize(encoding) { + const hash = this._hash.digest(encoding); + this.reset(); + return hash; + } + +} + +module.exports = hash_sha2_384; \ No newline at end of file diff --git a/fabric-common/test/HashPrimitives.js b/fabric-common/test/HashPrimitives.js new file mode 100644 index 0000000000..ad23ea4346 --- /dev/null +++ b/fabric-common/test/HashPrimitives.js @@ -0,0 +1,105 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const rewire = require('rewire'); +const HashPrimitives = rewire('../lib/HashPrimitives'); +const sinon = require('sinon'); + +require('chai').should(); + +describe('HashPrimitives', () => { + + describe('SHA2_256', () => { + let sandbox; + let revert; + + beforeEach(() => { + revert = []; + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + if (revert.length) { + revert.forEach(Function.prototype.call, Function.prototype.call); + } + sandbox.restore(); + }); + it('should call hash and return an insatnce of hash_sha2_256 (default to hex)', () => { + const mockHashFunction = sandbox.stub().returns('hash'); + const mockHash = sandbox.stub().returns({hash: mockHashFunction}); + revert.push(HashPrimitives.__set__('hash_sha2_256', mockHash)); + HashPrimitives.SHA2_256('data').should.equal('hash'); + sinon.assert.called(mockHash); + sinon.assert.calledWith(mockHashFunction, 'data', 'hex'); + }); + it('should call hash and return an insatnce of hash_sha2_256 (specify hex)', () => { + const mockHashFunction = sandbox.stub().returns('hash'); + const mockHash = sandbox.stub().returns({hash: mockHashFunction}); + revert.push(HashPrimitives.__set__('hash_sha2_256', mockHash)); + HashPrimitives.SHA2_256('data', 'hex').should.equal('hash'); + sinon.assert.called(mockHash); + sinon.assert.calledWith(mockHashFunction, 'data', 'hex'); + }); + it('should call hash and return an insatnce of hash_sha2_256 (specify null)', () => { + const mockHashFunction = sandbox.stub().returns('hash'); + const mockHash = sandbox.stub().returns({hash: mockHashFunction}); + revert.push(HashPrimitives.__set__('hash_sha2_256', mockHash)); + HashPrimitives.SHA2_256('data', null).should.equal('hash'); + sinon.assert.called(mockHash); + sinon.assert.calledWith(mockHashFunction, 'data', null); + }); + }); + + describe('SHA2_384', () => { + let sandbox; + let revert; + + beforeEach(() => { + revert = []; + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + if (revert.length) { + revert.forEach(Function.prototype.call, Function.prototype.call); + } + sandbox.restore(); + }); + it('should call hash and return an insatnce of hash_sha2_384', () => { + const mockHashFunction = sandbox.stub().returns('hash'); + const mockHash = sandbox.stub().returns({hash: mockHashFunction}); + revert.push(HashPrimitives.__set__('hash_sha2_384', mockHash)); + HashPrimitives.SHA2_384('data').should.equal('hash'); + sinon.assert.called(mockHash); + sinon.assert.calledWith(mockHashFunction, 'data'); + }); + it('should call hash and return an insatnce of hash_sha2_384 (specify hex)', () => { + const mockHashFunction = sandbox.stub().returns('hash'); + const mockHash = sandbox.stub().returns({hash: mockHashFunction}); + revert.push(HashPrimitives.__set__('hash_sha2_384', mockHash)); + HashPrimitives.SHA2_384('data', 'hex').should.equal('hash'); + sinon.assert.called(mockHash); + sinon.assert.calledWith(mockHashFunction, 'data', 'hex'); + }); + it('should call hash and return an insatnce of hash_sha2_384 (specify null)', () => { + const mockHashFunction = sandbox.stub().returns('hash'); + const mockHash = sandbox.stub().returns({hash: mockHashFunction}); + revert.push(HashPrimitives.__set__('hash_sha2_384', mockHash)); + HashPrimitives.SHA2_384('data', null).should.equal('hash'); + sinon.assert.called(mockHash); + sinon.assert.calledWith(mockHashFunction, 'data', null); + }); + }); + +}); \ No newline at end of file diff --git a/fabric-common/test/hash/hash_sha2_256.js b/fabric-common/test/hash/hash_sha2_256.js new file mode 100644 index 0000000000..a6e9be5115 --- /dev/null +++ b/fabric-common/test/hash/hash_sha2_256.js @@ -0,0 +1,72 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const rewire = require('rewire'); +const hash_sha2_256 = rewire('../../lib/hash/hash_sha2_256'); +const sinon = require('sinon'); + +require('chai').should(); + +describe('hash_sha2_256', () => { + let sandbox; + let revert; + let hash; + + beforeEach(() => { + revert = []; + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + if (revert.length) { + revert.forEach(Function.prototype.call, Function.prototype.call); + } + sandbox.restore(); + }); + + describe('#hash', () => { + it('should call the reset reset.update.finalize and return the correct value', () => { + hash = new hash_sha2_256(); + const finalizeStub = sandbox.stub().returns('finalize'); + const updateStub = sandbox.stub().returns({finalize: finalizeStub}); + const resetStub = sandbox.stub(hash, 'reset').returns({update: updateStub}); + hash.hash('data').should.equal('finalize'); + sinon.assert.called(resetStub); + sinon.assert.calledWith(updateStub, 'data'); + sinon.assert.calledWith(finalizeStub, 'hex'); + }); + }); + + describe('#reset', () => { + it('should call crypto.createHash', () => { + const cryptoStub = {createHash: sandbox.stub()}; + revert.push(hash_sha2_256.__set__('crypto', cryptoStub)); + hash = new hash_sha2_256(); + hash.reset(); + sinon.assert.calledWith(cryptoStub.createHash, 'sha256'); + }); + }); + + describe('#finalize', () => { + it('should call _hash.digest, reset and reurn the hash', () => { + hash = new hash_sha2_256(); + const digestStub = sandbox.stub().returns('hash'); + hash._hash = {digest: digestStub}; + const resetStub = sandbox.stub(hash, 'reset'); + hash.finalize('encoding').should.equal('hash'); + sinon.assert.calledWith(digestStub, 'encoding'); + sinon.assert.called(resetStub); + }); + }); +}); \ No newline at end of file diff --git a/fabric-common/test/hash/hash_sha2_384.js b/fabric-common/test/hash/hash_sha2_384.js new file mode 100644 index 0000000000..83f5a78e47 --- /dev/null +++ b/fabric-common/test/hash/hash_sha2_384.js @@ -0,0 +1,72 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const rewire = require('rewire'); +const hash_sha2_384 = rewire('../../lib/hash/hash_sha2_384'); +const sinon = require('sinon'); + +require('chai').should(); + +describe('hash_sha2_384', () => { + let sandbox; + let revert; + let hash; + + beforeEach(() => { + revert = []; + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + if (revert.length) { + revert.forEach(Function.prototype.call, Function.prototype.call); + } + sandbox.restore(); + }); + + describe('#hash', () => { + it('should call the reset reset.update.finalize and return the correct value', () => { + hash = new hash_sha2_384(); + const finalizeStub = sandbox.stub().returns('finalize'); + const updateStub = sandbox.stub().returns({finalize: finalizeStub}); + const resetStub = sandbox.stub(hash, 'reset').returns({update: updateStub}); + hash.hash('data').should.equal('finalize'); + sinon.assert.called(resetStub); + sinon.assert.calledWith(updateStub, 'data'); + sinon.assert.calledWith(finalizeStub, 'hex'); + }); + }); + + describe('#reset', () => { + it('should call crypto.createHash', () => { + const cryptoStub = {createHash: sandbox.stub()}; + revert.push(hash_sha2_384.__set__('crypto', cryptoStub)); + hash = new hash_sha2_384(); + hash.reset(); + sinon.assert.calledWith(cryptoStub.createHash, 'sha384'); + }); + }); + + describe('#finalize', () => { + it('should call _hash.digest, reset and reurn the hash', () => { + hash = new hash_sha2_384(); + const digestStub = sandbox.stub().returns('hash'); + hash._hash = {digest: digestStub}; + const resetStub = sandbox.stub(hash, 'reset'); + hash.finalize('encoding').should.equal('hash'); + sinon.assert.calledWith(digestStub, 'encoding'); + sinon.assert.called(resetStub); + }); + }); +}); \ No newline at end of file diff --git a/test/integration/signTransactionOffline.js b/test/integration/signTransactionOffline.js index 94fce7da22..8a41bea426 100644 --- a/test/integration/signTransactionOffline.js +++ b/test/integration/signTransactionOffline.js @@ -14,7 +14,7 @@ const test = _test(tape); const FabricCAService = require('fabric-ca-client'); const Client = require('fabric-client'); -const hash = require('fabric-client/lib/hash'); +const {HashPrimitives} = require('fabric-common'); const jsrsa = require('jsrsasign'); const {KEYUTIL} = jsrsa; @@ -68,7 +68,7 @@ function _preventMalleability(sig, curveParams) { */ function sign(privateKey, proposalBytes, algorithm, keySize) { const hashAlgorithm = algorithm.toUpperCase(); - const hashFunction = hash[`${hashAlgorithm}_${keySize}`]; + const hashFunction = HashPrimitives[`${hashAlgorithm}_${keySize}`]; const ecdsaCurve = elliptic.curves[`p${keySize}`]; const ecdsa = new EC(ecdsaCurve); const key = KEYUTIL.getKey(privateKey);