diff --git a/build/tasks/ca.js b/build/tasks/ca.js index e83db7b899..924af452ca 100644 --- a/build/tasks/ca.js +++ b/build/tasks/ca.js @@ -20,7 +20,6 @@ const DEPS = [ 'fabric-client/lib/impl/ecdsa/*', 'fabric-client/lib/impl/CryptoKeyStore.js', 'fabric-client/lib/impl/FileKeyValueStore.js', - 'fabric-client/lib/msp/identity.js', 'fabric-client/lib/msp/msp.js', 'fabric-client/types/tsconfig.json', 'fabric-client/types/base.d.ts' diff --git a/fabric-client/lib/Channel.js b/fabric-client/lib/Channel.js index fa83904512..0af0ec1caf 100755 --- a/fabric-client/lib/Channel.js +++ b/fabric-client/lib/Channel.js @@ -29,7 +29,7 @@ const MSPManager = require('./msp/msp-manager.js'); const Policy = require('./Policy.js'); const Constants = require('./Constants.js'); const CollectionConfig = require('./SideDB.js'); -const {Identity} = require('./msp/identity.js'); +const {Identity} = require('fabric-common'); const ChannelHelper = require('./utils/ChannelHelper'); const ImplicitMetaPolicy_Rule = {0: 'ANY', 1: 'ALL', 2: 'MAJORITY'}; diff --git a/fabric-client/lib/ChannelEventHub.js b/fabric-client/lib/ChannelEventHub.js index 6f42efabfe..932b1c00f3 100644 --- a/fabric-client/lib/ChannelEventHub.js +++ b/fabric-client/lib/ChannelEventHub.js @@ -18,7 +18,7 @@ const utils = require('./utils.js'); const clientUtils = require('./client-utils.js'); const Constants = require('./Constants.js'); const logger = utils.getLogger('ChannelEventHub.js'); -const {Identity} = require('./msp/identity'); +const {Identity} = require('fabric-common'); const TransactionID = require('./TransactionID'); const util = require('util'); diff --git a/fabric-client/lib/Client.js b/fabric-client/lib/Client.js index fa16b67ae8..c20d8aa48e 100644 --- a/fabric-client/lib/Client.js +++ b/fabric-client/lib/Client.js @@ -10,7 +10,7 @@ const sdkUtils = require('./utils.js'); const clientUtils = require('./client-utils.js'); -const {KeyValueStore} = require('fabric-common'); +const {KeyValueStore, Signer, SigningIdentity} = require('fabric-common'); const BaseClient = require('./BaseClient.js'); const User = require('./User.js'); const Channel = require('./Channel.js'); @@ -19,7 +19,6 @@ const Peer = require('./Peer.js'); const ChannelEventHub = require('./ChannelEventHub'); const Orderer = require('./Orderer.js'); const TransactionID = require('./TransactionID.js'); -const {Signer, SigningIdentity} = require('./msp/identity.js'); const crypto = require('crypto'); const util = require('util'); diff --git a/fabric-client/lib/User.js b/fabric-client/lib/User.js index 7d8a43bf01..1f30c22196 100644 --- a/fabric-client/lib/User.js +++ b/fabric-client/lib/User.js @@ -16,12 +16,8 @@ const util = require('util'); const sdkUtils = require('./utils.js'); -const {CryptoAlgorithms} = require('fabric-common'); +const {CryptoAlgorithms, Identity, Signer, SigningIdentity} = require('fabric-common'); const logger = sdkUtils.getLogger('User.js'); -const idModule = require('./msp/identity.js'); -const Identity = idModule.Identity; -const SigningIdentity = idModule.SigningIdentity; -const Signer = idModule.Signer; /** * The User class represents users that have been enrolled and represented by diff --git a/fabric-client/lib/msp/identity.js b/fabric-client/lib/msp/identity.js deleted file mode 100755 index c046b87d9a..0000000000 --- a/fabric-client/lib/msp/identity.js +++ /dev/null @@ -1,248 +0,0 @@ -/* -# Copyright IBM Corp. All Rights Reserved. -# -# SPDX-License-Identifier: Apache-2.0 -*/ -'use strict'; - -const fabprotos = require('fabric-protos'); - -/** - * This interface is shared within the peer and client API of the membership service provider. - * Identity interface defines operations associated to a "certificate". - * That is, the public part of the identity could be thought to be a certificate, - * and offers solely signature verification capabilities. This is to be used - * at the client side when validating certificates that endorsements are signed - * with, and verifying signatures that correspond to these certificates. - * - * @class - */ -class Identity { - /** - * @param {string} certificate HEX string for the PEM encoded certificate - * @param {module:api.Key} publicKey The public key represented by the certificate - * @param {string} mspId The associated MSP's mspId that manages this identity - * @param {module:api.CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital - * signature algorithm - */ - constructor(certificate, publicKey, mspId, cryptoSuite) { - - if (!certificate) { - throw new Error('Missing required parameter "certificate".'); - } - - if (!mspId) { - throw new Error('Missing required parameter "mspId".'); - } - - this._certificate = certificate; - this._publicKey = publicKey; - this._mspId = mspId; - this._cryptoSuite = cryptoSuite; - } - - /** - * Returns the identifier of the Membser Service Provider that manages - * this identity in terms of being able to understand the key algorithms - * and have access to the trusted roots needed to validate it - * @returns {string} - */ - getMSPId() { - return this._mspId; - } - - /** - * This uses the rules that govern this identity to validate it. - * E.g., if it is a fabric TCert implemented as identity, validate - * will check the TCert signature against the assumed root certificate - * authority. - * @returns {boolean} - */ - isValid() { - return true; - } - - /** - * Returns the organization units this identity is related to - * as long as this is public information. In certain implementations - * this could be implemented by certain attributes that are publicly - * associated to that identity, or the identifier of the root certificate - * authority that has provided signatures on this certificate. - * Examples: - * - OrganizationUnit of a fabric-tcert that was signed by TCA under name - * "Organization 1", would be "Organization 1". - * - OrganizationUnit of an alternative implementation of tcert signed by a public - * CA used by organization "Organization 1", could be provided in the clear - * as part of that tcert structure that this call would be able to return. - * @returns {string} - */ - getOrganizationUnits() { - return 'dunno!'; - } - - /** - * Verify a signature over some message using this identity as reference - * @param {byte[]} msg The message to be verified - * @param {byte[]} signature The signature generated against the message "msg" - * @param {Object} opts Options include 'policy' and 'label' TODO (not implemented yet) - */ - verify(msg, signature, opts) { - // TODO: retrieve the publicKey from the certificate - if (!this._publicKey) { - throw new Error('Missing public key for this Identity'); - } - if (!this._cryptoSuite) { - throw new Error('Missing cryptoSuite for this Identity'); - } - return this._cryptoSuite.verify(this._publicKey, signature, msg); - } - - /** - * Verify attributes against the given attribute spec - * TODO: when this method's design is finalized - */ - verifyAttributes(proof, attributeProofSpec) { - return true; - } - - /** - * Converts this identity to bytes - * @returns {Buffer} protobuf-based serialization with two fields: "mspid" and "certificate PEM bytes" - */ - serialize() { - const serializedIdentity = new fabprotos.msp.SerializedIdentity(); - serializedIdentity.setMspid(this.getMSPId()); - serializedIdentity.setIdBytes(Buffer.from(this._certificate)); - return serializedIdentity.toBuffer(); - } -} - -/** - * Signer is an interface for an opaque private key that can be used for signing operations - * - * @class - */ -class Signer { - /** - * @param {module:api.CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital - * signature algorithm - * @param {module:api.Key} key The private key - */ - constructor(cryptoSuite, key) { - if (!cryptoSuite) { - throw new Error('Missing required parameter "cryptoSuite"'); - } - - if (!key) { - throw new Error('Missing required parameter "key" for private key'); - } - - this._cryptoSuite = cryptoSuite; - this._key = key; - } - - /** - * Returns the public key corresponding to the opaque, private key - * - * @returns {module:api.Key} The public key corresponding to the private key - */ - getPublicKey() { - return this._key.getPublicKey(); - } - - /** - * Signs digest with the private key. - * - * Hash implements the SignerOpts interface and, in most cases, one can - * simply pass in the hash function used as opts. Sign may also attempt - * to type assert opts to other types in order to obtain algorithm - * specific values. - * - * Note that when a signature of a hash of a larger message is needed, - * the caller is responsible for hashing the larger message and passing - * the hash (as digest) and the hash function (as opts) to Sign. - * - * @param {byte[]} digest The message to sign - * @param {Object} opts - * hashingFunction: the function to use to hash - */ - sign(digest, opts) { - return this._cryptoSuite.sign(this._key, digest, opts); - } -} - -/** - * SigningIdentity is an extension of Identity to cover signing capabilities. E.g., signing identity - * should be requested in the case of a client who wishes to sign proposal responses and transactions - * - * @class - */ -class SigningIdentity extends Identity { - /** - * @param {string} certificate HEX string for the PEM encoded certificate - * @param {module:api.Key} publicKey The public key represented by the certificate - * @param {string} mspId The associated MSP's ID that manages this identity - * @param {module:api.CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital - * signature algorithm - * @param {Signer} signer The signer object encapsulating the opaque private key and the corresponding - * digital signature algorithm to be used for signing operations - */ - constructor(certificate, publicKey, mspId, cryptoSuite, signer) { - if (!certificate) { - throw new Error('Missing required parameter "certificate".'); - } - if (!publicKey) { - throw new Error('Missing required parameter "publicKey".'); - } - if (!mspId) { - throw new Error('Missing required parameter "mspId".'); - } - if (!cryptoSuite) { - throw new Error('Missing required parameter "cryptoSuite".'); - } - super(certificate, publicKey, mspId, cryptoSuite); - - if (!signer) { - throw new Error('Missing required parameter "signer".'); - } - - this._signer = signer; - } - - /** - * Signs digest with the private key contained inside the signer. - * - * @param {byte[]} msg The message to sign - * @param {Object} opts Options object for the signing, contains one field 'hashFunction' that allows - * different hashing algorithms to be used. If not present, will default to the hash function - * configured for the identity's own crypto suite object - */ - sign(msg, opts) { - // calculate the hash for the message before signing - let hashFunction; - if (opts && opts.hashFunction) { - if (typeof opts.hashFunction !== 'function') { - throw new Error('The "hashFunction" field must be a function'); - } - - hashFunction = opts.hashFunction; - } else { - hashFunction = this._cryptoSuite.hash.bind(this._cryptoSuite); - } - - const digest = hashFunction(msg); - return this._signer.sign(Buffer.from(digest, 'hex'), null); - } - - static isInstance(object) { - return object._certificate && - object._publicKey && - object._mspId && - object._cryptoSuite && - object._signer; - } -} - -module.exports.Identity = Identity; -module.exports.SigningIdentity = SigningIdentity; -module.exports.Signer = Signer; diff --git a/fabric-client/lib/msp/msp.js b/fabric-client/lib/msp/msp.js index f41830a744..7dba66fc7b 100755 --- a/fabric-client/lib/msp/msp.js +++ b/fabric-client/lib/msp/msp.js @@ -5,10 +5,7 @@ */ 'use strict'; -const {CryptoAlgorithms} = require('fabric-common'); -const idModule = require('./identity.js'); -const Identity = idModule.Identity; -const SigningIdentity = idModule.SigningIdentity; +const {CryptoAlgorithms, Identity, SigningIdentity} = require('fabric-common'); const utils = require('../utils.js'); const logger = utils.getLogger('msp.js'); diff --git a/fabric-client/test/Channel.js b/fabric-client/test/Channel.js index f8b36b2952..3da3070f31 100644 --- a/fabric-client/test/Channel.js +++ b/fabric-client/test/Channel.js @@ -27,7 +27,7 @@ const Channel = require('fabric-client/lib/Channel'); const ChannelRewire = rewire('fabric-client/lib/Channel'); const ChannelEventHub = require('fabric-client/lib/ChannelEventHub'); const Client = require('fabric-client/lib/Client'); -const {Identity, SigningIdentity} = require('fabric-client/lib/msp/identity'); +const {Identity, SigningIdentity} = require('fabric-common'); const MSP = require('fabric-client/lib/msp/msp'); const MSPManager = require('fabric-client/lib/msp/msp-manager'); const Orderer = require('fabric-client/lib/Orderer'); diff --git a/fabric-client/test/Client.js b/fabric-client/test/Client.js index 9e73036665..f36b360b3c 100644 --- a/fabric-client/test/Client.js +++ b/fabric-client/test/Client.js @@ -19,7 +19,7 @@ const rewire = require('rewire'); const Client = rewire('../lib/Client'); const NetworkConfig = require('../lib/impl/NetworkConfig_1_0'); const fs = require('fs'); -const {Identity} = require('../lib/msp/identity'); +const {Identity} = require('fabric-common'); const Package = require('../lib/Package'); const path = require('path'); const User = require('../lib/User'); diff --git a/fabric-client/test/TransactionID.js b/fabric-client/test/TransactionID.js index cb36305904..f6230ae35d 100644 --- a/fabric-client/test/TransactionID.js +++ b/fabric-client/test/TransactionID.js @@ -18,7 +18,7 @@ const rewire = require('rewire'); const TransactionID = require('../lib/TransactionID'); const TransactionIDRewire = rewire('../lib/TransactionID'); -const Identity = require('../lib/msp/identity').Identity; +const {Identity} = require('fabric-common'); const User = require('../lib/User'); const Utils = require('../lib/utils'); diff --git a/fabric-common/index.js b/fabric-common/index.js index 32f7102263..8439e2a166 100644 --- a/fabric-common/index.js +++ b/fabric-common/index.js @@ -8,14 +8,20 @@ const Config = require('./lib/Config'); const CryptoAlgorithms = require('./lib/CryptoAlgorithms'); const CryptoSuite = require('./lib/CryptoSuite'); const HashPrimitives = require('./lib/HashPrimitives'); +const Identity = require('./lib/Identity'); const Key = require('./lib/Key'); const KeyValueStore = require('./lib/KeyValueStore'); +const Signer = require('./lib/Signer'); +const SigningIdentity = require('./lib/SigningIdentity'); module.exports = { Config, CryptoAlgorithms, CryptoSuite, HashPrimitives, + Identity, Key, - KeyValueStore + KeyValueStore, + Signer, + SigningIdentity }; diff --git a/fabric-common/lib/Identity.js b/fabric-common/lib/Identity.js new file mode 100644 index 0000000000..4206a30b3a --- /dev/null +++ b/fabric-common/lib/Identity.js @@ -0,0 +1,121 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +const fabprotos = require('fabric-protos'); + +/** + * This interface is shared within the peer and client API of the membership service provider. + * Identity interface defines operations associated to a "certificate". + * That is, the public part of the identity could be thought to be a certificate, + * and offers solely signature verification capabilities. This is to be used + * at the client side when validating certificates that endorsements are signed + * with, and verifying signatures that correspond to these certificates. + * + * @class + */ +class Identity { + /** + * @param {string} certificate HEX string for the PEM encoded certificate + * @param {module:api.Key} publicKey The public key represented by the certificate + * @param {string} mspId The associated MSP's mspId that manages this identity + * @param {module:api.CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital + * signature algorithm + */ + constructor(certificate, publicKey, mspId, cryptoSuite) { + + if (!certificate) { + throw new Error('Missing required parameter "certificate".'); + } + + if (!mspId) { + throw new Error('Missing required parameter "mspId".'); + } + + this._certificate = certificate; + this._publicKey = publicKey; + this._mspId = mspId; + this._cryptoSuite = cryptoSuite; + } + + /** + * Returns the identifier of the Membser Service Provider that manages + * this identity in terms of being able to understand the key algorithms + * and have access to the trusted roots needed to validate it + * @returns {string} + */ + getMSPId() { + return this._mspId; + } + + /** + * This uses the rules that govern this identity to validate it. + * E.g., if it is a fabric TCert implemented as identity, validate + * will check the TCert signature against the assumed root certificate + * authority. + * @returns {boolean} + */ + isValid() { + return true; + } + + /** + * Returns the organization units this identity is related to + * as long as this is public information. In certain implementations + * this could be implemented by certain attributes that are publicly + * associated to that identity, or the identifier of the root certificate + * authority that has provided signatures on this certificate. + * Examples: + * - OrganizationUnit of a fabric-tcert that was signed by TCA under name + * "Organization 1", would be "Organization 1". + * - OrganizationUnit of an alternative implementation of tcert signed by a public + * CA used by organization "Organization 1", could be provided in the clear + * as part of that tcert structure that this call would be able to return. + * @returns {string} + */ + getOrganizationUnits() { + return 'dunno!'; + } + + /** + * Verify a signature over some message using this identity as reference + * @param {byte[]} msg The message to be verified + * @param {byte[]} signature The signature generated against the message "msg" + * @param {Object} opts Options include 'policy' and 'label' TODO (not implemented yet) + */ + verify(msg, signature, opts) { + // TODO: retrieve the publicKey from the certificate + if (!this._publicKey) { + throw new Error('Missing public key for this Identity'); + } + if (!this._cryptoSuite) { + throw new Error('Missing cryptoSuite for this Identity'); + } + return this._cryptoSuite.verify(this._publicKey, signature, msg); + } + + /** + * Verify attributes against the given attribute spec + * TODO: when this method's design is finalized + */ + verifyAttributes(proof, attributeProofSpec) { + return true; + } + + /** + * Converts this identity to bytes + * @returns {Buffer} protobuf-based serialization with two fields: "mspid" and "certificate PEM bytes" + */ + serialize() { + const serializedIdentity = new fabprotos.msp.SerializedIdentity(); + serializedIdentity.setMspid(this.getMSPId()); + serializedIdentity.setIdBytes(Buffer.from(this._certificate)); + return serializedIdentity.toBuffer(); + } +} + +module.exports = Identity; \ No newline at end of file diff --git a/fabric-common/lib/Signer.js b/fabric-common/lib/Signer.js new file mode 100644 index 0000000000..4ef7f0da20 --- /dev/null +++ b/fabric-common/lib/Signer.js @@ -0,0 +1,63 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +/** + * Signer is an interface for an opaque private key that can be used for signing operations + * + * @class + */ +class Signer { + /** + * @param {module:api.CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital + * signature algorithm + * @param {module:api.Key} key The private key + */ + constructor(cryptoSuite, key) { + if (!cryptoSuite) { + throw new Error('Missing required parameter "cryptoSuite"'); + } + + if (!key) { + throw new Error('Missing required parameter "key" for private key'); + } + + this._cryptoSuite = cryptoSuite; + this._key = key; + } + + /** + * Returns the public key corresponding to the opaque, private key + * + * @returns {module:api.Key} The public key corresponding to the private key + */ + getPublicKey() { + return this._key.getPublicKey(); + } + + /** + * Signs digest with the private key. + * + * Hash implements the SignerOpts interface and, in most cases, one can + * simply pass in the hash function used as opts. Sign may also attempt + * to type assert opts to other types in order to obtain algorithm + * specific values. + * + * Note that when a signature of a hash of a larger message is needed, + * the caller is responsible for hashing the larger message and passing + * the hash (as digest) and the hash function (as opts) to Sign. + * + * @param {byte[]} digest The message to sign + * @param {Object} opts + * hashingFunction: the function to use to hash + */ + sign(digest, opts) { + return this._cryptoSuite.sign(this._key, digest, opts); + } +} + +module.exports = Signer; \ No newline at end of file diff --git a/fabric-common/lib/SigningIdentity.js b/fabric-common/lib/SigningIdentity.js new file mode 100644 index 0000000000..60fd6afa25 --- /dev/null +++ b/fabric-common/lib/SigningIdentity.js @@ -0,0 +1,83 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +const Identity = require('./Identity'); + +/** + * SigningIdentity is an extension of Identity to cover signing capabilities. E.g., signing identity + * should be requested in the case of a client who wishes to sign proposal responses and transactions + * + * @class + */ +class SigningIdentity extends Identity { + /** + * @param {string} certificate HEX string for the PEM encoded certificate + * @param {module:api.Key} publicKey The public key represented by the certificate + * @param {string} mspId The associated MSP's ID that manages this identity + * @param {module:api.CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital + * signature algorithm + * @param {Signer} signer The signer object encapsulating the opaque private key and the corresponding + * digital signature algorithm to be used for signing operations + */ + constructor(certificate, publicKey, mspId, cryptoSuite, signer) { + if (!certificate) { + throw new Error('Missing required parameter "certificate".'); + } + if (!publicKey) { + throw new Error('Missing required parameter "publicKey".'); + } + if (!mspId) { + throw new Error('Missing required parameter "mspId".'); + } + if (!cryptoSuite) { + throw new Error('Missing required parameter "cryptoSuite".'); + } + super(certificate, publicKey, mspId, cryptoSuite); + + if (!signer) { + throw new Error('Missing required parameter "signer".'); + } + + this._signer = signer; + } + + /** + * Signs digest with the private key contained inside the signer. + * + * @param {byte[]} msg The message to sign + * @param {Object} opts Options object for the signing, contains one field 'hashFunction' that allows + * different hashing algorithms to be used. If not present, will default to the hash function + * configured for the identity's own crypto suite object + */ + sign(msg, opts) { + // calculate the hash for the message before signing + let hashFunction; + if (opts && opts.hashFunction) { + if (typeof opts.hashFunction !== 'function') { + throw new Error('The "hashFunction" field must be a function'); + } + + hashFunction = opts.hashFunction; + } else { + hashFunction = this._cryptoSuite.hash.bind(this._cryptoSuite); + } + + const digest = hashFunction(msg); + return this._signer.sign(Buffer.from(digest, 'hex'), null); + } + + static isInstance(object) { + return object._certificate && + object._publicKey && + object._mspId && + object._cryptoSuite && + object._signer; + } +} + +module.exports = SigningIdentity; \ No newline at end of file diff --git a/fabric-common/package.json b/fabric-common/package.json index 1ce86d4104..953c2894d8 100644 --- a/fabric-common/package.json +++ b/fabric-common/package.json @@ -2,10 +2,10 @@ "name": "fabric-common", "version": "2.0.0-snapshot", "tag": "unstable", - "description" : "This package encapsulates the common code used by the `fabric-ca-client`, `fabric-client` packages.", - "keywords" :[ - "blockchain", - "hyperledger" + "description": "This package encapsulates the common code used by the `fabric-ca-client`, `fabric-client` packages.", + "keywords": [ + "blockchain", + "hyperledger" ], "main": "index.js", "repository": { @@ -25,10 +25,12 @@ }, "devDependencies": { "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", "mocha": "^5.2.0", "nyc": "^12.0.2", "rewire": "^4.0.1", - "sinon": "^6.1.3" + "sinon": "^6.1.3", + "sinon-chai": "^3.3.0" }, "nyc": { "exclude": [ diff --git a/fabric-common/test/Identity.js b/fabric-common/test/Identity.js new file mode 100644 index 0000000000..0bb269efad --- /dev/null +++ b/fabric-common/test/Identity.js @@ -0,0 +1,128 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const {CryptoSuite, Identity, Key} = require('..'); +const fabprotos = require('fabric-protos'); +const fs = require('fs'); +const path = require('path'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); +chai.should(); +chai.use(chaiAsPromised); +chai.use(sinonChai); +const sinon = require('sinon'); + +const certificateAsPEM = fs.readFileSync(path.join(__dirname, 'data', 'cert.pem')); +const certificateAsBuffer = Buffer.from(certificateAsPEM); +const certificateAsHex = certificateAsBuffer.toString('hex'); +const mspId = 'Org1MSP'; +const message = Buffer.from('hello world!'); +const signature = Buffer.from('my signature!'); + +describe('Identity', () => { + + let identity; + let mockPublicKey; + let mockCryptoSuite; + + beforeEach(() => { + mockPublicKey = sinon.createStubInstance(Key); + mockCryptoSuite = sinon.createStubInstance(CryptoSuite); + identity = new Identity(certificateAsHex, mockPublicKey, mspId, mockCryptoSuite); + }); + + describe('#constructor', () => { + + it('should throw if no certificate', () => { + (() => { + new Identity(null, mockPublicKey, mspId, mockCryptoSuite); + }).should.throw(/Missing required parameter "certificate"./); + }); + + it('should throw if no MSP ID', () => { + (() => { + new Identity(certificateAsHex, mockPublicKey, null, mockCryptoSuite); + }).should.throw(/Missing required parameter "mspId"./); + }); + + }); + + describe('#getMSPId', () => { + + it('should return the MSP ID', () => { + identity.getMSPId().should.equal(mspId); + }); + + }); + + describe('#isValid', () => { + + it('should return true', () => { + identity.isValid().should.be.true; + }); + + }); + + describe('#getOrganizationUnits', () => { + + it('should return dunno!', () => { + identity.getOrganizationUnits().should.equal('dunno!'); + }); + + }); + + describe('#verify', () => { + + it('should throw if no public key', () => { + identity = new Identity(certificateAsHex, null, mspId, mockCryptoSuite); + (() => { + identity.verify(message, signature); + }).should.throw(/Missing public key for this Identity/); + }); + + it('should throw if no public key', () => { + identity = new Identity(certificateAsHex, mockPublicKey, mspId, null); + (() => { + identity.verify(message, signature); + }).should.throw(/Missing cryptoSuite for this Identity/); + }); + + it('should handle a successful verify by the crypto suite', async () => { + mockCryptoSuite.verify.withArgs(mockPublicKey, signature, message).resolves(); + await identity.verify(message, signature); + mockCryptoSuite.verify.should.have.been.calledOnceWithExactly(mockPublicKey, signature, message); + }); + + it('should handle an unsuccessful verify by the crypto suite', async () => { + mockCryptoSuite.verify.withArgs(mockPublicKey, signature, message).rejects(new Error('such error')); + await identity.verify(message, signature).should.be.rejectedWith(/such error/); + }); + + }); + + describe('#verifyAttributes', () => { + + it('should return true', () => { + identity.verifyAttributes().should.be.true; + }); + + }); + + describe('#serialize', () => { + + it('should serialize the identity to a buffer', () => { + const buffer = identity.serialize(); + buffer.should.be.an.instanceOf(Buffer); + const serializedIdentity = fabprotos.msp.SerializedIdentity.decode(buffer); + serializedIdentity.getMspid().should.equal(mspId); + serializedIdentity.getIdBytes().toBuffer().toString().should.equal(certificateAsHex); + }); + + }); + +}); \ No newline at end of file diff --git a/fabric-common/test/Signer.js b/fabric-common/test/Signer.js new file mode 100644 index 0000000000..7a5a53d86b --- /dev/null +++ b/fabric-common/test/Signer.js @@ -0,0 +1,76 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const {CryptoSuite, Key, Signer} = require('..'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); +chai.should(); +chai.use(chaiAsPromised); +chai.use(sinonChai); +const sinon = require('sinon'); + +const digest = Buffer.from('hello world!'); +const opts = { + hashingFunction: sinon.stub() +}; + +describe('Signer', () => { + + let signer; + let mockPrivateKey; + let mockPublicKey; + let mockCryptoSuite; + + beforeEach(() => { + mockPrivateKey = sinon.createStubInstance(Key); + mockPublicKey = sinon.createStubInstance(Key); + mockPrivateKey.getPublicKey.returns(mockPublicKey); + mockCryptoSuite = sinon.createStubInstance(CryptoSuite); + signer = new Signer(mockCryptoSuite, mockPrivateKey); + }); + + describe('#constructor', () => { + + it('should throw if no crypto suite', () => { + (() => { + new Signer(null, mockPrivateKey); + }).should.throw(/Missing required parameter "cryptoSuite"/); + }); + + it('should throw if no MSP ID', () => { + (() => { + new Signer(mockCryptoSuite, null); + }).should.throw(/Missing required parameter "key" for private key/); + }); + + }); + + describe('#getPublicKey', () => { + + it('should return the public key', () => { + signer.getPublicKey().should.equal(mockPublicKey); + }); + + }); + + describe('#sign', () => { + + it('should handle a successful sign by the crypto suite', async () => { + mockCryptoSuite.sign.withArgs(mockPrivateKey, digest, opts).resolves(); + await signer.sign(digest, opts); + mockCryptoSuite.sign.should.have.been.calledOnceWithExactly(mockPrivateKey, digest, opts); + }); + + it('should handle an unsuccessful sign by the crypto suite', async () => { + mockCryptoSuite.sign.withArgs(mockPrivateKey, digest, opts).rejects(new Error('such error')); + await signer.sign(digest, opts).should.be.rejectedWith(/such error/); + }); + + }); + +}); \ No newline at end of file diff --git a/fabric-common/test/SigningIdentity.js b/fabric-common/test/SigningIdentity.js new file mode 100644 index 0000000000..0cb1e5d7d6 --- /dev/null +++ b/fabric-common/test/SigningIdentity.js @@ -0,0 +1,114 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const {CryptoSuite, HashPrimitives, SigningIdentity, Key, Signer} = require('..'); +const fs = require('fs'); +const path = require('path'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinonChai = require('sinon-chai'); +chai.should(); +chai.use(chaiAsPromised); +chai.use(sinonChai); +const sinon = require('sinon'); + +const certificateAsPEM = fs.readFileSync(path.join(__dirname, 'data', 'cert.pem')); +const certificateAsBuffer = Buffer.from(certificateAsPEM); +const certificateAsHex = certificateAsBuffer.toString('hex'); +const mspId = 'Org1MSP'; +const message = Buffer.from('hello world!'); + +describe('SigningIdentity', () => { + + let signingIdentity; + let mockPublicKey; + let mockCryptoSuite; + let mockSigner; + + beforeEach(() => { + mockPublicKey = sinon.createStubInstance(Key); + mockCryptoSuite = sinon.createStubInstance(CryptoSuite); + mockSigner = sinon.createStubInstance(Signer); + signingIdentity = new SigningIdentity(certificateAsHex, mockPublicKey, mspId, mockCryptoSuite, mockSigner); + }); + + describe('#constructor', () => { + + it('should throw if no certificate', () => { + (() => { + new SigningIdentity(null, mockPublicKey, mspId, mockCryptoSuite, mockSigner); + }).should.throw(/Missing required parameter "certificate"./); + }); + + it('should throw if no public key', () => { + (() => { + new SigningIdentity(certificateAsHex, null, mspId, mockCryptoSuite, mockSigner); + }).should.throw(/Missing required parameter "publicKey"./); + }); + + it('should throw if no MSP ID', () => { + (() => { + new SigningIdentity(certificateAsHex, mockPublicKey, null, mockCryptoSuite, mockSigner); + }).should.throw(/Missing required parameter "mspId"./); + }); + + it('should throw if no crypto suite', () => { + (() => { + new SigningIdentity(certificateAsHex, mockPublicKey, mspId, null, mockSigner); + }).should.throw(/Missing required parameter "cryptoSuite"./); + }); + + it('should throw if no signer', () => { + (() => { + new SigningIdentity(certificateAsHex, mockPublicKey, mspId, mockCryptoSuite, null); + }).should.throw(/Missing required parameter "signer"./); + }); + + }); + + describe('#sign', () => { + + it('should throw if options specified, but hash function specified is not a function', () => { + (() => { + signingIdentity.sign(message, {hashFunction: 'lulz not a function'}); + }).should.throw(/The "hashFunction" field must be a function/); + }); + + it('should handle a successful sign by the signer using the default hash function', async () => { + const digest = HashPrimitives.SHA2_256(message); + const hashFunction = sinon.stub(); + hashFunction.withArgs(message).returns(digest); + mockCryptoSuite.hash = hashFunction; + mockSigner.sign.withArgs(digest, null).resolves(); + await signingIdentity.sign(message); + mockSigner.sign.should.have.been.calledOnceWithExactly(sinon.match((buffer) => { + return buffer.toString('hex') === digest; + }), null); + }); + + it('should handle a successful sign by the signer using the specified hash function', async () => { + const digest = HashPrimitives.SHA2_256(message); + const hashFunction = sinon.stub(); + hashFunction.withArgs(message).returns(digest); + mockSigner.sign.resolves(); + await signingIdentity.sign(message, {hashFunction}); + mockSigner.sign.should.have.been.calledOnceWithExactly(sinon.match((buffer) => { + return buffer.toString('hex') === digest; + }), null); + }); + + it('should handle an unsuccessful sign by the signer', async () => { + const digest = HashPrimitives.SHA2_256(message); + const hashFunction = sinon.stub(); + hashFunction.withArgs(message).returns(digest); + mockSigner.sign.rejects(new Error('such error')); + await signingIdentity.sign(message, {hashFunction}).should.have.been.rejectedWith(/such error/); + }); + + }); + +}); \ No newline at end of file diff --git a/fabric-common/test/data/cert.pem b/fabric-common/test/data/cert.pem new file mode 100644 index 0000000000..53d5d01fe8 --- /dev/null +++ b/fabric-common/test/data/cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICVDCCAfqgAwIBAgIUfU2mX13EKc1LRR5Fet2iwFPn+IMwCgYIKoZIzj0EAwIw +XTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK +EwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMQ4wDAYDVQQDEwV0bHNjYTAe +Fw0xOTAyMDQxMTUxMDBaFw0yMDAyMDQxMTU2MDBaMF0xCzAJBgNVBAYTAlVTMRcw +FQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEUMBIGA1UEChMLSHlwZXJsZWRnZXIxDzAN +BgNVBAsTBmNsaWVudDEOMAwGA1UEAxMFYWRtaW4wWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAAR+08XWiZZWFkrhX4nC2s0W0QwfIZpMgdgYnLkYcSSEiw+KAuuZBgAc +zgmP/o35XPLXeGQ2UlMCpZnVmDSQ5QvFo4GXMIGUMA4GA1UdDwEB/wQEAwIDqDAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNV +HQ4EFgQUcnSSvNcFSuKRNlLqmqct0Kea7aMwHwYDVR0jBBgwFoAUV1uJWaYdWC+h +t4RtFzxQe/F9jswwFQYDVR0RBA4wDIcEqS7fWocEfwAAATAKBggqhkjOPQQDAgNI +ADBFAiEAt1tv0n42KtkmpUNNuD1ji6hJTBVc6hQL1GcQbVgWW0ACIH6F6vA26TwI +N7FqJHSDzyVAcz5B9VuO6PMB1v/cGs1M +-----END CERTIFICATE----- \ No newline at end of file diff --git a/package.json b/package.json index 631afc2b00..e01387d163 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "rimraf": "2.6.2", "run-sequence": "^2.2.1", "sinon": "6.1.3", + "sinon-chai": "^3.3.0", "tap-colorize": "^1.2.0", "tape": "^4.5.1", "tape-promise": "^3.0.0", diff --git a/test/integration/fabric-ca-services-tests.js b/test/integration/fabric-ca-services-tests.js index f5f0431062..bb3ee90951 100644 --- a/test/integration/fabric-ca-services-tests.js +++ b/test/integration/fabric-ca-services-tests.js @@ -30,9 +30,7 @@ const http = require('http'); const testUtil = require('../unit/util.js'); const LocalMSP = require('fabric-ca-client/lib/msp/msp.js'); -const idModule = require('fabric-ca-client/lib/msp/identity.js'); -const SigningIdentity = idModule.SigningIdentity; -const Signer = idModule.Signer; +const {Signer, SigningIdentity} = require('fabric-common'); const User = require('fabric-ca-client/lib/User.js'); // var keyValStorePath = testUtil.KVS; diff --git a/test/unit/identity.js b/test/unit/identity.js index e0992012db..25e5f79779 100644 --- a/test/unit/identity.js +++ b/test/unit/identity.js @@ -12,14 +12,10 @@ const test = _test(tape); const testutil = require('./util.js'); const utils = require('fabric-client/lib/utils.js'); -const {CryptoAlgorithms} = require('fabric-common'); +const {CryptoAlgorithms, Identity, Signer, SigningIdentity} = require('fabric-common'); const jsrsa = require('jsrsasign'); const KEYUTIL = jsrsa.KEYUTIL; -const idModule = require('fabric-client/lib/msp/identity.js'); -const Identity = idModule.Identity; -const Signer = idModule.Signer; -const SigningIdentity = idModule.SigningIdentity; const MSP = require('fabric-client/lib/msp/msp.js'); const ecdsaKey = require('fabric-client/lib/impl/ecdsa/key.js'); diff --git a/test/unit/msp.js b/test/unit/msp.js index 0c8cc4e888..4b881df9ba 100644 --- a/test/unit/msp.js +++ b/test/unit/msp.js @@ -17,8 +17,7 @@ const utils = rewire('fabric-client/lib/utils.js'); const testutil = require('./util.js'); const MSP = require('fabric-client/lib/msp/msp.js'); const MSPM = require('fabric-client/lib/msp/msp-manager.js'); -const idModule = require('fabric-client/lib/msp/identity.js'); -const Identity = idModule.Identity; +const {Identity} = require('fabric-common'); const mspProto = require('fabric-protos').msp;