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();
});
/**