Skip to content

Commit

Permalink
FAB-1221 Implement SigningIdentity
Browse files Browse the repository at this point in the history
using this interface as per MSP/BCCSP design to abstract out
signature operations such that client code does not need to
touch lower-level BCCSP APIs, making it modular and possible
to swap in different impls of MSP and BCCSP

Using these new classes will be done in a subsequent PR

Patch 4: fixed a small bug and added one more test
Patch 5: fixed gulp failure due to trailing space

Change-Id: I70cf88d99881ca08f56459593569f263cdf07006
Signed-off-by: Jim Zhang <jzhang@us.ibm.com>
  • Loading branch information
jimthematrix committed Jan 5, 2017
1 parent fbb3ae3 commit 00ede37
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 5 deletions.
3 changes: 2 additions & 1 deletion hfc/lib/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

/**
Expand Down
2 changes: 1 addition & 1 deletion hfc/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

Expand Down
93 changes: 92 additions & 1 deletion hfc/lib/msp/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

3 changes: 2 additions & 1 deletion hfc/lib/msp/msp.js
Original file line number Diff line number Diff line change
@@ -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');

Expand Down
95 changes: 94 additions & 1 deletion test/unit/headless-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');


Expand Down Expand Up @@ -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: [],
Expand All @@ -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();
});

Expand Down

0 comments on commit 00ede37

Please sign in to comment.