diff --git a/fabric-ca-client/lib/FabricCAClientImpl.js b/fabric-ca-client/lib/FabricCAClientImpl.js index a94ead8483..dd1c2a9d9b 100644 --- a/fabric-ca-client/lib/FabricCAClientImpl.js +++ b/fabric-ca-client/lib/FabricCAClientImpl.js @@ -73,14 +73,35 @@ var FabricCAServices = class { /** * Register the member and return an enrollment secret. - * @param {Object} req Registration request with the following fields: enrollmentID, roles, registrar - * @param {Member} registrar The identity of the registrar (i.e. who is performing the registration) - * @returns Promise for the enrollmentSecret - * @ignore + * @param {Object} req Registration request with the following fields: + *
- enrollmentID {string}. ID which will be used for enrollment + *
- group {string}. Group to which this user will be assigned, like a company or an organization + *
- attrs {{@link KeyValueAttribute}[]}. Array of key/value attributes to assign to the user. + * @param registrar {User}. The identity of the registrar (i.e. who is performing the registration) + * @returns {Promise} The enrollment secret to use when this user enrolls */ register(req, registrar) { - var self = this; + if (typeof req === 'undefined' || req === null) { + throw new Error('Missing required argument "request"'); + } + if (typeof req.enrollmentID === 'undefined' || req.enrollmentID === null) { + throw new Error('Missing required argument "request.enrollmentID"'); + } + + if (typeof registrar === 'undefined' || registrar === null) { + throw new Error('Missing required argument "registrar"'); + } + + if (typeof registrar.getName !== 'function') { + throw new Error('Argument "registrar" must be an instance of the class "User", but is found to be missing a method "getName()"'); + } + + if (typeof registrar.getSigningIdentity !== 'function') { + throw new Error('Argument "registrar" must be an instance of the class "User", but is found to be missing a method "getSigningIdentity()"'); + } + + return this._fabricCAClient.register(req.enrollmentID, 'client', req.group, req.attrs, registrar.getName(), registrar.getSigningIdentity()); } /** @@ -216,7 +237,6 @@ var FabricCAClient = class { * @param {string} connect_opts.protocol The protocol to use (either HTTP or HTTPS) * @param {string} connect_opts.hostname The hostname of the Fabric CA server endpoint * @param {number} connect_opts.port The port of the Fabric CA server endpoint - * @param {Buffer[]} connect_opts.ca An array of trusted certificates in PEM format * @throws Will throw an error if connection options are missing or invalid * */ @@ -237,7 +257,6 @@ var FabricCAClient = class { } else { this._port = (connect_opts.protocol === 'http' ? 80 : 443); } - this._ca = (connect_opts.ca) ? connect_opts.ca : null; this._baseAPI = '/api/v1/cfssl/'; @@ -256,18 +275,20 @@ var FabricCAClient = class { * @param {string} group Group to which this user will be assigned * @param {KeyValueAttribute[]} attrs Array of key/value attributes to assign to the user * @param {string} callerID The ID of the user who is registering this user + * @param {SigningIdentity} signingIdentity The instance of a SigningIdentity encapsulating the + * signing certificate, hash algorithm and signature algorithm * @returns {Promise} The enrollment secret to use when this user enrolls */ - register(enrollmentID, role, group, attrs, callerID) { + register(enrollmentID, role, group, attrs, callerID, signingIdentity) { var self = this; var numArgs = arguments.length; return new Promise(function (resolve, reject) { //all arguments are required - if (numArgs < 5) { + if (numArgs < 6) { reject(new Error('Missing required parameters. \'enrollmentID\', \'role\', \'group\', \'attrs\', \ - and \'callerID\' are all required.')); + \'callerID\' and \'signingIdentity\' are all required.')); } @@ -284,8 +305,9 @@ var FabricCAClient = class { port: self._port, path: self._baseAPI + 'register', method: 'POST', - //auth: enrollmentID + ':' + enrollmentSecret, - ca: self._ca + headers: { + Authorization: FabricCAClient.generateAuthToken(regRequest, signingIdentity) + } }; var request = self._httpClient.request(requestOptions, function (response) { @@ -307,8 +329,10 @@ var FabricCAClient = class { try { var regResponse = JSON.parse(payload); if (regResponse.success) { - //we want the result field which is Base64-encoded PEM - return resolve(regResponse.result); + // we want the result field which is Base64-encoded secret. + // TODO: Keith said this may be changed soon for 'result' to be the raw secret + // without Base64-encoding it + return resolve(Buffer.from(regResponse.result, 'base64').toString()); } else { return reject(new Error( util.format('Register failed with errors [%s]', JSON.stringify(regResponse.errors)))); @@ -332,13 +356,24 @@ var FabricCAClient = class { } /** - * Generate authorization token required for accessing fabric-cop APIs - * @param {string} privKey The pem-encoded private key used for signing - * @param {string} X509Key The pem-encoded X509 certificate associated with privKey - * @param {string} reqBody The body of the request to sign as part of the token + * Generate authorization token required for accessing fabric-ca APIs */ - static generateAuthToken(privKey, X509Key, reqBody) { + static generateAuthToken(reqBody, signingIdentity) { + // sometimes base64 encoding results in trailing one or two "=" as padding + var trim = function(string) { + return string.replace(/=*$/, ''); + }; + + // specific signing procedure is according to: + // https://github.com/hyperledger/fabric-ca/blob/master/util/util.go#L213 + var cert = trim(Buffer.from(signingIdentity._certificate).toString('base64')); + var body = trim(Buffer.from(JSON.stringify(reqBody)).toString('base64')); + + var bodyAndcert = body + '.' + cert; + var sig = signingIdentity.sign(bodyAndcert); + var b64Sign = trim(Buffer.from(sig, 'hex').toString('base64')); + return cert + '.' + b64Sign; } /** @@ -366,8 +401,7 @@ var FabricCAClient = class { port: self._port, path: self._baseAPI + 'enroll', method: 'POST', - auth: enrollmentID + ':' + enrollmentSecret, - ca: self._ca + auth: enrollmentID + ':' + enrollmentSecret }; var enrollRequest = { diff --git a/test/integration/fabriccopservices-tests.js b/test/integration/fabriccopservices-tests.js index 8d49d9b923..95d1c8d4df 100644 --- a/test/integration/fabriccopservices-tests.js +++ b/test/integration/fabriccopservices-tests.js @@ -28,42 +28,21 @@ var fs = require('fs'); var path = require('path'); var testUtil = require('../unit/util.js'); var utils = require('fabric-client/lib/utils.js'); +var LocalMSP = require('fabric-client/lib/msp/msp.js'); +var idModule = require('fabric-client/lib/msp/identity.js'); +var SigningIdentity = idModule.SigningIdentity; +var Signer = idModule.Signer; +var User = require('fabric-client/lib/User.js'); var keyValStorePath = testUtil.KVS; - var FabricCAServices = require('fabric-ca-client/lib/FabricCAClientImpl'); var FabricCAClient = FabricCAServices.FabricCAClient; var enrollmentID = 'testUser'; -var enrollmentSecret = 'user1'; +var enrollmentSecret; var csr = fs.readFileSync(path.resolve(__dirname, '../fixtures/fabriccop/enroll-csr.pem')); - -test('FabricCAClient: Test enroll With Static CSR', function (t) { - - var client = new FabricCAClient({ - protocol: 'http', - hostname: '127.0.0.1', - port: 7054 - }); - - // - return client.enroll(enrollmentID, enrollmentSecret, csr.toString()) - .then(function (pem) { - t.comment(pem); - t.pass('Successfully invoked enroll API with enrollmentID \'' + enrollmentID + '\''); - //check that we got back the expected certificate - var cert = new X509(); - cert.readCertPEM(pem); - t.comment(cert.getSubjectString()); - t.equal(cert.getSubjectString(), '/CN=' + enrollmentID, 'Subject should be /CN=' + enrollmentID); - }) - .catch(function (err) { - t.fail('Failed to enroll \'' + enrollmentID + '\'. ' + err); - }); -}); - /** * FabricCAServices class tests */ @@ -83,26 +62,60 @@ test('FabricCAServices: Test enroll() With Dynamic CSR', function (t) { enrollmentSecret: 'adminpw' }; + var eResult, client, member; return cop.enroll(req) - .then( - function (enrollment) { - + .then((enrollment) => { t.pass('Successfully enrolled \'' + req.enrollmentID + '\'.'); + eResult = enrollment; //check that we got back the expected certificate var cert = new X509(); cert.readCertPEM(enrollment.certificate); t.comment(cert.getSubjectString()); t.equal(cert.getSubjectString(), '/CN=' + req.enrollmentID, 'Subject should be /CN=' + req.enrollmentID); - }, - function (err) { - t.fail('Failed to enroll \'' + req.enrollmentID + '\'. ' + err); - } - ); + return cop.cryptoPrimitives.importKey(enrollment.certificate); + }).then((pubKey) => { + t.pass('Successfully imported public key from the resulting enrollment certificate'); + + var msp = new LocalMSP({ + id: 'DEFAULT', + cryptoSuite: cop.cryptoPrimitives + }); + + var signingIdentity = new SigningIdentity('testSigningIdentity', eResult.certificate, pubKey, msp, new Signer(msp.cryptoSuite, eResult.key)); + + return cop._fabricCAClient.register(enrollmentID, 'client', 'bank_a', [], 'admin', signingIdentity); + }).then((secret) => { + t.comment(secret); + enrollmentSecret = secret; // to be used in the next test case + + t.pass('Successfully invoked register API with enrollmentID \'' + enrollmentID + '\''); + + return hfc.newDefaultKeyValueStore({ + path: testUtil.KVS + }); + }).then((store) => { + t.comment('Successfully constructed a state store'); + + client = new hfc(); + client.setStateStore(store); + member = new User('adminX', client); + return member.setEnrollment(eResult.key, eResult.certificate); + }).then(() => { + t.comment('Successfully constructed a user object based on the enrollment'); + + return cop.register({enrollmentID: 'testUserX', group: 'bank_a'}, member); + }).then((secret) => { + t.pass('Successfully enrolled "testUserX" in group "bank_a" with enrollment secret returned: ' + secret); + t.end(); + }).catch((err) => { + t.fail('Failed at ' + err.stack ? err.stack : err); + t.end(); + }); }); -test('FabricCAClient: Test register', function (t) { +test('FabricCAClient: Test enroll With Static CSR', function (t) { var client = new FabricCAClient({ protocol: 'http', @@ -110,16 +123,20 @@ test('FabricCAClient: Test register', function (t) { port: 7054 }); - var enrollmentID = 'testRegisterUser'; - - - return client.register(enrollmentID, 'client', 'bank_a', [], 'admin') - .then(function (secret) { - t.comment(secret); - t.pass('Successfully invoked register API with enrollmentID \'' + enrollmentID + '\''); - + t.comment(util.format('Sending enroll request for user %s with enrollment secret %s', enrollmentID, enrollmentSecret)); + return client.enroll(enrollmentID, enrollmentSecret, csr.toString()) + .then(function (pem) { + t.comment(pem); + t.pass('Successfully invoked enroll API with enrollmentID \'' + enrollmentID + '\''); + //check that we got back the expected certificate + var cert = new X509(); + cert.readCertPEM(pem); + t.comment(cert.getSubjectString()); + t.equal(cert.getSubjectString(), '/CN=' + enrollmentID, 'Subject should be /CN=' + enrollmentID); + t.end(); }) .catch(function (err) { - t.fail('Failed to register \'' + enrollmentID + '\'. ' + err); + t.fail('Failed to enroll \'' + enrollmentID + '\'. ' + err); + t.end(); }); }); diff --git a/test/unit/fabric-ca-client.js b/test/unit/fabric-ca-client.js index e7f01a5255..ce6ba76efa 100644 --- a/test/unit/fabric-ca-client.js +++ b/test/unit/fabric-ca-client.js @@ -147,6 +147,62 @@ test('FabricCAClient: Test _pemToDer static method',function(t){ t.end(); }); +test('FabricCAServices: Test register() function', function(t) { + var cop = new FabricCAServices('http://localhost:7054'); + + t.throws( + () => { + cop.register(); + }, + /Missing required argument "request"/, + 'Must fail if missing request argument' + ); + t.throws( + () => { + cop.register({}); + }, + /Missing required argument "request.enrollmentID"/, + 'Must fail if missing request.enrollmentID argument' + ); + t.throws( + () => { + cop.register({dummy: 'value'}); + }, + /Missing required argument "request.enrollmentID"/, + 'Must fail if missing request argument' + ); + t.throws( + () => { + cop.register({enrollmentID: 'testUser'}); + }, + /Missing required argument "registrar"/, + 'Must fail if missing registrar argument' + ); + t.throws( + () => { + cop.register({enrollmentID: 'testUser'}, {}); + }, + /Argument "registrar" must be an instance of the class "User", but is found to be missing a method "getName/, + 'Must fail if registrar argument is not a User object' + ); + t.throws( + () => { + cop.register({enrollmentID: 'testUser'}, { getName: function() { return 'dummy';} }); + }, + /Argument "registrar" must be an instance of the class "User", but is found to be missing a method "getSigningIdentity/, + 'Must fail if registrar argument is not a User object' + ); + t.doesNotThrow( + () => { + cop.register({enrollmentID: 'testUser'}, { getName: function() { return 'dummy'; }, getSigningIdentity: function() { return 'dummy'; } }); + }, + null, + 'Should pass the argument checking but would fail when the register call tries to assemble the auth token' + ); + + t.end(); +}); + test('FabricCAServices: Test _parseURL() function', function (t) { var goodHost = 'www.example.com'; @@ -207,6 +263,8 @@ test('FabricCAServices: Test _parseURL() function', function (t) { /InvalidURL: url must start with http or https./, 'Throw error for missing protocol' ); + + t.end(); }); /**