diff --git a/hfc/lib/User.js b/hfc/lib/User.js index a97048e3a1..11c883db15 100644 --- a/hfc/lib/User.js +++ b/hfc/lib/User.js @@ -20,7 +20,8 @@ var util = require('util'); var sdkUtils = require('./utils.js'); var api = require('./api.js'); var logger = sdkUtils.getLogger('Client.js'); -var Identity = require('./msp/identity.js'); +var idModule = require('./msp/identity.js'); +var Identity = idModule.Identity; var MSP = require('./msp/msp.js'); /** diff --git a/hfc/lib/api.js b/hfc/lib/api.js index 7e28dfdd5a..8e19a418e8 100644 --- a/hfc/lib/api.js +++ b/hfc/lib/api.js @@ -116,7 +116,7 @@ module.exports.CryptoSuite = class { * @param {byte[]} msg Source message to be hashed * @param {Object} opts * algorithm: an identifier for the algorithm to be used, such as "SHA3" - * @returns {byte[]} The hashed digest + * @returns {string} The hashed digest in hexidecimal string encoding */ hash(msg, opts) {} diff --git a/hfc/lib/msp/identity.js b/hfc/lib/msp/identity.js index 3316230f01..d7302d388b 100644 --- a/hfc/lib/msp/identity.js +++ b/hfc/lib/msp/identity.js @@ -117,4 +117,95 @@ var Identity = class { } }; -module.exports = Identity; +/** + * Signer is an interface for an opaque private key that can be used for signing operations + * + * @class + */ +var Signer = class { + /** + * @param {CryptoSuite} cryptoSuite The underlying {@link CryptoSuite} implementation for the digital + * signature algorithm + * @param {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 {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 + */ +var SigningIdentity = class extends Identity { + /** + * @param {string} id Identifier of this identity object + * @param {string} certificate HEX string for the PEM encoded certificate + * @param {Key} publicKey The public key represented by the certificate + * @param {Signer} signer The signer object encapsulating the opaque private key and the corresponding + * digital signature algorithm to be used for signing operations + * @param {MSP} msp The associated MSP that manages this identity + */ + constructor(id, certificate, publicKey, msp, signer) { + super(id, certificate, publicKey, msp); + + 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 + */ + sign(msg) { + // calculate the hash for the message before signing + var digest = this._msp.cryptoSuite.hash(msg); + return this._signer.sign(msg, null); + } +}; + +module.exports.Identity = Identity; +module.exports.SigningIdentity = SigningIdentity; +module.exports.Signer = Signer; + diff --git a/hfc/lib/msp/msp.js b/hfc/lib/msp/msp.js index f553616d3d..3b496cf1ba 100644 --- a/hfc/lib/msp/msp.js +++ b/hfc/lib/msp/msp.js @@ -1,7 +1,8 @@ 'use strict'; var api = require('../api.js'); -var Identity = require('./identity.js'); +var idModule = require('./identity.js'); +var Identity = idModule.Identity; var utils = require('../utils.js'); var logger = utils.getLogger('msp.js'); diff --git a/test/unit/headless-tests.js b/test/unit/headless-tests.js index 2d29b551e5..6c1b48e34b 100644 --- a/test/unit/headless-tests.js +++ b/test/unit/headless-tests.js @@ -1075,6 +1075,26 @@ var TEST_CERT_PEM = '-----BEGIN CERTIFICATE-----' + 'UWUxIC0CIQDNyHQAwzhw+512meXRwG92GfpzSBssDKLdwlrqiHOu5A==' + '-----END CERTIFICATE-----'; +var TEST_KEY_PRIVATE_PEM = '-----BEGIN PRIVATE KEY-----' + +'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZYMvf3w5VkzzsTQY' + +'I8Z8IXuGFZmmfjIX2YSScqCvAkihRANCAAS6BhFgW/q0PzrkwT5RlWTt41VgXLgu' + +'Pv6QKvGsW7SqK6TkcCfxsWoSjy6/r1SzzTMni3J8iQRoJ3roPmoxPLK4' + +'-----END PRIVATE KEY-----'; +var TEST_KEY_PRIVATE_CERT_PEM = '-----BEGIN CERTIFICATE-----' + +'MIICEDCCAbagAwIBAgIUXoY6X7jIpHAAgL267xHEpVr6NSgwCgYIKoZIzj0EAwIw' + +'fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh' + +'biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK' + +'BgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTcwMTAzMDEyNDAw' + +'WhcNMTgwMTAzMDEyNDAwWjAQMQ4wDAYDVQQDEwVhZG1pbjBZMBMGByqGSM49AgEG' + +'CCqGSM49AwEHA0IABLoGEWBb+rQ/OuTBPlGVZO3jVWBcuC4+/pAq8axbtKorpORw' + +'J/GxahKPLr+vVLPNMyeLcnyJBGgneug+ajE8srijfzB9MA4GA1UdDwEB/wQEAwIF' + +'oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd' + +'BgNVHQ4EFgQU9BUt7QfgDXx9g6zpzCyJGxXsNM0wHwYDVR0jBBgwFoAUF2dCPaqe' + +'gj/ExR2fW8OZ0bWcSBAwCgYIKoZIzj0EAwIDSAAwRQIgcWQbMzluyZsmvQCvGzPg' + +'f5B7ECxK0kdmXPXIEBiizYACIQD2x39Q4oVwO5uL6m3AVNI98C2LZWa0g2iea8wk' + +'BAHpeA==' + +'-----END CERTIFICATE-----'; + var jsrsa = require('jsrsasign'); var KEYUTIL = jsrsa.KEYUTIL; var ECDSA = jsrsa.ECDSA; @@ -2046,7 +2066,10 @@ test('FabricCOPServices: Test _parseURL() function', function (t) { ); }); -var Identity = require('hfc/lib/msp/identity.js'); +var idModule = require('hfc/lib/msp/identity.js'); +var Identity = idModule.Identity; +var Signer = idModule.Signer; +var SigningIdentity = idModule.SigningIdentity; var MSP = require('hfc/lib/msp/msp.js'); @@ -2132,6 +2155,62 @@ test('\n\n ** Identity class tests **\n\n', function (t) { 'Checking required config parameter "cryptoSuite" for MSP constructor' ); + t.throws( + function() { + var signer = new Signer(); + }, + /Missing required parameter "cryptoSuite"/, + 'Checking required parameter "cryptoSuite"' + ); + + t.throws( + function() { + var signer = new Signer('blah'); + }, + /Missing required parameter "key" for private key/, + 'Checking required parameter "key"' + ); + + t.throws( + function() { + new SigningIdentity(); + }, + /Missing required parameter "id"/, + 'Checking required input parameters' + ); + + t.throws( + function() { + new SigningIdentity('id'); + }, + /Missing required parameter "certificate"/, + 'Checking required input parameters' + ); + + t.throws( + function() { + new SigningIdentity('id', 'cert'); + }, + /Missing required parameter "publicKey"/, + 'Checking required input parameters' + ); + + t.throws( + function() { + new SigningIdentity('id', 'cert', 'pubKey'); + }, + /Missing required parameter "msp"/, + 'Checking required input parameters' + ); + + t.throws( + function() { + new SigningIdentity('id', 'cert', 'pubKey', 'msp'); + }, + /Missing required parameter "signer"/, + 'Checking required input parameters' + ); + // test identity serialization and deserialization var mspImpl = new MSP({ trustedCerts: [], @@ -2151,6 +2230,20 @@ test('\n\n ** Identity class tests **\n\n', function (t) { t.equal(dsID._publicKey.isPrivate(), false, 'Identity class function tests: deserialized public key'); t.equal(dsID._publicKey._key.pubKeyHex, '0452a75e1ee105da7ab3d389fda69d8a04f5cf65b305b49cec7cdbdeb91a585cf87bef5a96aa9683d96bbabfe60d8cc6f5db9d0bc8c58d56bb28887ed81c6005ac', 'Identity class function tests: deserialized public key ecparam check'); + // manually construct a key based on the saved privKeyHex and pubKeyHex + var f = KEYUTIL.getKey(TEST_KEY_PRIVATE_PEM); + var testKey = new ecdsaKey(f, 256); + var pubKey = testKey.getPublicKey(); + + var signer = new Signer(cryptoUtils, testKey); + t.equal(signer.getPublicKey().isPrivate(), false, 'Test Signer class getPublicKey() method'); + + var signingID = new SigningIdentity('testSigningIdentity', TEST_KEY_PRIVATE_CERT_PEM, pubKey, mspImpl, signer); + + var sig = signingID.sign(TEST_MSG); + t.equal(cryptoUtils.verify(pubKey, sig.toDER(), TEST_MSG), true, 'Test SigningIdentity sign() method'); + t.equal(signingID.verify(TEST_MSG, sig.toDER()), true, 'Test Identity verify() method'); + t.end(); });