Skip to content

Commit

Permalink
Add networkAdminPrivateKeyFile option to composer network start (hy…
Browse files Browse the repository at this point in the history
…perledger-archives#4181)

Ensures that, when the network is started using network admin
certificates instead of enrollment secrets, the network admin
card(s) produced can actually be imported and used.

Signed-off-by: Mark S. Lewis <[email protected]>
  • Loading branch information
bestbeforetoday authored and Simon Stone committed Jun 22, 2018
1 parent d80992a commit 8c90b3c
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 41 deletions.
28 changes: 18 additions & 10 deletions packages/composer-admin/lib/adminconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ class AdminConnection {
* @param {String} networkName - Name of the business network to start
* @param {String} networkVersion - Version of the business network to start
* @param {Object} [startOptions] connector specific start options
* networkAdmins: [ { userName, certificate } , { userName, enrollmentSecret }]
* networkAdmins: [ { userName, certificate, privateKey } , { userName, enrollmentSecret }]
*
* @return {Promise} A promise that will be fufilled when the business network has been
* deployed - with a MAP of cards key is name
Expand Down Expand Up @@ -463,22 +463,30 @@ class AdminConnection {
if (networkAdmins){
networkAdmins.forEach( (networkAdmin) =>{

let metadata= {
const metadata = {
version : 1,
userName : networkAdmin.userName,
businessNetwork : networkName
};

let newCard;
if (networkAdmin.enrollmentSecret ){
metadata.enrollmentSecret = networkAdmin.enrollmentSecret ;
newCard = new IdCard(metadata,connectionProfile);
} else {
newCard = new IdCard(metadata,connectionProfile);
newCard.setCredentials({ certificate : networkAdmin.certificate });
const enrollmentSecret = networkAdmin.enrollmentSecret;
if (enrollmentSecret) {
metadata.enrollmentSecret = enrollmentSecret;
}
createdCards.set(networkAdmin.userName,newCard);

const newCard = new IdCard(metadata, connectionProfile);

const certificate = networkAdmin.certificate;
if (certificate) {
const credentials = { certificate: certificate };
const privateKey = networkAdmin.privateKey;
if (privateKey) {
credentials.privateKey = privateKey;
}
newCard.setCredentials(credentials);
}

createdCards.set(networkAdmin.userName,newCard);
});
}
LOG.exit(method);
Expand Down
42 changes: 41 additions & 1 deletion packages/composer-admin/test/adminconnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ describe('AdminConnection', () => {
const identityName = 'admin';
const networkAdmins = [
{ userName: 'admin', enrollmentSecret: 'adminpw' },
{ userName: 'adminc', certificate: 'certcertcert'}
{ userName: 'adminc', certificate: 'certcertcert' },
{ userName: 'admincp', certificate: 'certcertcert', privateKey: 'keykeykey' },
];
const bootstrapTransactions = [
{
Expand Down Expand Up @@ -297,6 +298,18 @@ describe('AdminConnection', () => {
timestamp : '1970-01-01T00:00:00.000Z',
transactionId : sinon.match.string
},
{
$class : 'org.hyperledger.composer.system.AddParticipant',
resources : [
{
$class : 'org.hyperledger.composer.system.NetworkAdmin',
participantId : 'admincp'
}
],
targetRegistry : 'resource:org.hyperledger.composer.system.ParticipantRegistry#org.hyperledger.composer.system.NetworkAdmin',
timestamp : '1970-01-01T00:00:00.000Z',
transactionId : sinon.match.string
},
{
$class : 'org.hyperledger.composer.system.IssueIdentity',
identityName : 'admin',
Expand All @@ -310,6 +323,13 @@ describe('AdminConnection', () => {
participant : 'resource:org.hyperledger.composer.system.NetworkAdmin#adminc',
timestamp : '1970-01-01T00:00:00.000Z',
transactionId : sinon.match.string
},
{
$class : 'org.hyperledger.composer.system.BindIdentity',
certificate : 'certcertcert',
participant : 'resource:org.hyperledger.composer.system.NetworkAdmin#admincp',
timestamp : '1970-01-01T00:00:00.000Z',
transactionId : sinon.match.string
}
]
};
Expand Down Expand Up @@ -417,6 +437,26 @@ describe('AdminConnection', () => {
});
});

it('should create network admin cards', async () => {
const cards = await adminConnection.start(networkName, networkVersion, { networkAdmins: networkAdmins });
cards.size.should.equal(networkAdmins.length);
networkAdmins.forEach((networkAdmin) => {
const card = cards.get(networkAdmin.userName);
should.exist(card);

if (networkAdmin.enrollmentSecret) {
card.getEnrollmentCredentials().secret.should.equal(networkAdmin.enrollmentSecret);
} else {
const expectedCredentials = { certificate: networkAdmin.certificate };
const privateKey = networkAdmin.privateKey;
if (privateKey) {
expectedCredentials.privateKey = privateKey;
}
card.getCredentials().should.deep.equal(expectedCredentials);
}
});
});

});

describe('#upgrade', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/composer-cli/lib/cmds/network/startCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ module.exports.builder = function (yargs) {
optionsFile: { alias: 'O', required: false, describe: 'A file containing options that are specific to connection', type: 'string' },
networkAdmin: { alias: 'A', required: true, description: 'The identity name of the business network administrator', type: 'string' },
networkAdminCertificateFile: { alias: 'C', required: false, description: 'The certificate of the business network administrator', type: 'string' },
networkAdminPrivateKeyFile: { alias: 'K', required: false, description: 'The private key of the business network administrator', type: 'string' },
networkAdminEnrollSecret: { alias: 'S', required: false, description: 'The enrollment secret for the business network administrator', type: 'string', default: undefined },
file: { alias: 'f', required: false, description: 'File name of the card to be created', type: 'string'}
});

// enforce the option after these options
yargs.requiresArg(['file','networkName','networkVersion','networkAdmin','networkAdminCertificateFile','networkAdminEnrollSecret','card']);
yargs.requiresArg(['file','networkName','networkVersion','networkAdmin','networkAdminCertificateFile','networkAdminPrivateKeyFile','networkAdminEnrollSecret','card']);

yargs.conflicts('C','S');

Expand Down
21 changes: 14 additions & 7 deletions packages/composer-cli/lib/cmds/utils/cmdutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,28 @@ class CmdUtil {
/**
* Parse the business network administrators which have been specified using certificate files.
* @param {string[]} networkAdmins Identity names for the business network administrators.
* @param {string[]} networkAdminCertificateFiles Certificate files for the business network administrators.
* @param {string[]} certificateFiles Certificate files for the business network administrators.
* @param {string[]} privateKeyFiles Private key files for the business network administrators.
* @return {Object[]} The business network administrators.
*/
static parseNetworkAdminsWithCertificateFiles(networkAdmins, networkAdminCertificateFiles) {
static parseNetworkAdminsWithCertificateFiles(networkAdmins, certificateFiles, privateKeyFiles) {

// Go through each network admin.
return networkAdmins.map((networkAdmin, index) => {

// Load the specified certificate for the network admin.
const certificateFile = networkAdminCertificateFiles[index];
const certificate = fs.readFileSync(certificateFile, { encoding: 'utf8' });
return {
const certificate = fs.readFileSync(certificateFiles[index], { encoding: 'utf8' });
const networkAdminInfo = {
userName: networkAdmin,
certificate
certificate: certificate
};

const privateKeyFile = privateKeyFiles[index];
if (privateKeyFile) {
networkAdminInfo.privateKey = fs.readFileSync(privateKeyFile, { encoding: 'utf8' });
}

return networkAdminInfo;
});

}
Expand Down Expand Up @@ -151,6 +157,7 @@ class CmdUtil {
// Convert the arguments into arrays.
const networkAdmins = CmdUtil.arrayify(argv.networkAdmin);
const networkAdminCertificateFiles = CmdUtil.arrayify(argv.networkAdminCertificateFile);
const networkAdminPrivateKeyFiles = CmdUtil.arrayify(argv.networkAdminPrivateKeyFile);
const networkAdminEnrollSecrets = CmdUtil.arrayify(argv.networkAdminEnrollSecret);
const files = CmdUtil.arrayify(argv.file);

Expand All @@ -167,7 +174,7 @@ class CmdUtil {
// Check that enough certificate files have been specified.
let result;
if (networkAdmins.length === networkAdminCertificateFiles.length) {
result = CmdUtil.parseNetworkAdminsWithCertificateFiles(networkAdmins, networkAdminCertificateFiles);
result = CmdUtil.parseNetworkAdminsWithCertificateFiles(networkAdmins, networkAdminCertificateFiles, networkAdminPrivateKeyFiles);
}

// Check that enough enrollment secrets have been specified.
Expand Down
12 changes: 9 additions & 3 deletions packages/composer-cli/test/network/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,21 @@ describe('composer start network CLI unit tests', function () {
});

it('should correctly execute with all required parameters and certificate file', function () {
const readFileSyncStub = sandbox.stub(fs,'readFileSync');

const certificate = 'this-is-a-certificate-honest-guv';
sandbox.stub(fs,'readFileSync').withArgs('certificate-file').returns(certificate);
readFileSyncStub.withArgs('certificate-file').returns(certificate);

const privateKey = 'this-is-a-private-key-honest-guv';
readFileSyncStub.withArgs('private-key-file').returns(privateKey);

let argv = {
card: 'cardname',
networkName: networkName,
networkVersion: networkVersion,
networkAdmin: 'admin',
networkAdminCertificateFile: 'certificate-file'
networkAdminCertificateFile: 'certificate-file',
networkAdminPrivateKeyFile: 'private-key-file'
};
return StartCmd.handler(argv).then(result => {
argv.thePromise.should.be.a('promise');
Expand All @@ -113,7 +119,7 @@ describe('composer start network CLI unit tests', function () {
sinon.assert.calledOnce(mockAdminConnection.start);
sinon.assert.calledWith(mockAdminConnection.start, networkName, networkVersion,
{
networkAdmins: [{ certificate: certificate, userName: 'admin' }]
networkAdmins: [{ certificate: certificate, privateKey: privateKey, userName: 'admin' }]
});
});
});
Expand Down
76 changes: 57 additions & 19 deletions packages/composer-cli/test/utils/cmdutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,25 @@ chai.use(require('chai-as-promised'));

describe('composer transaction cmdutils unit tests', () => {

const pem1 = '-----BEGIN CERTIFICATE-----\nsuch admin1\n-----END CERTIFICATE-----\n';
const pem2 = '-----BEGIN CERTIFICATE-----\nsuch admin2\n-----END CERTIFICATE-----\n';
const pem3 = '-----BEGIN CERTIFICATE-----\nsuch admin3\n-----END CERTIFICATE-----\n';
const cert1 = '-----BEGIN CERTIFICATE-----\nsuch admin1\n-----END CERTIFICATE-----\n';
const cert2 = '-----BEGIN CERTIFICATE-----\nsuch admin2\n-----END CERTIFICATE-----\n';
const cert3 = '-----BEGIN CERTIFICATE-----\nsuch admin3\n-----END CERTIFICATE-----\n';

const key1 = '-----BEGIN PRIVATE KEY-----\nsuch admin1\n-----END PRIVATE KEY-----\n';
const key2 = '-----BEGIN PRIVATE KEY-----\nsuch admin2\n-----END PRIVATE KEY-----\n';

let sandbox;
let fsStub;

beforeEach(() => {
sandbox = sinon.sandbox.create();
fsStub = sandbox.stub(fs, 'readFileSync');
fs.readFileSync.withArgs('admin1.pem').returns(pem1);
fs.readFileSync.withArgs('admin2.pem').returns(pem2);
fs.readFileSync.withArgs('admin3.pem').returns(pem3);
fsStub.withArgs('admin1-pub.pem').returns(cert1);
fsStub.withArgs('admin2-pub.pem').returns(cert2);
fsStub.withArgs('admin3-pub.pem').returns(cert3);
fsStub.withArgs('admin1-priv.pem').returns(key1);
fsStub.withArgs('admin2-priv.pem').returns(key2);
fsStub.withArgs('admin3-priv.pem').throws('Tried to read admin3-priv.pem');
});


Expand Down Expand Up @@ -167,24 +173,30 @@ describe('composer transaction cmdutils unit tests', () => {
describe('#parseNetworkAdminsWithCertificateFiles', () => {

it('should parse a single network admin', () => {
const result = CmdUtil.parseNetworkAdminsWithCertificateFiles(['admin1'], ['admin1.pem']);
const result = CmdUtil.parseNetworkAdminsWithCertificateFiles(['admin1'], ['admin1-pub.pem'], ['admin1-priv.pem']);
result.should.deep.equal([{
userName: 'admin1',
certificate: pem1
certificate: cert1,
privateKey: key1
}]);
});

it('should parse multiple network admins', () => {
const result = CmdUtil.parseNetworkAdminsWithCertificateFiles(['admin1', 'admin2', 'admin3'], ['admin1.pem', 'admin2.pem', 'admin3.pem']);
const result = CmdUtil.parseNetworkAdminsWithCertificateFiles(
['admin1', 'admin2', 'admin3'],
['admin1-pub.pem', 'admin2-pub.pem', 'admin3-pub.pem'],
['admin1-priv.pem', 'admin2-priv.pem']);
result.should.deep.equal([{
userName: 'admin1',
certificate: pem1
certificate: cert1,
privateKey: key1
}, {
userName: 'admin2',
certificate: pem2
certificate: cert2,
privateKey: key2
}, {
userName: 'admin3',
certificate: pem3
certificate: cert3
}]);
});

Expand Down Expand Up @@ -227,23 +239,47 @@ describe('composer transaction cmdutils unit tests', () => {
(() => {
CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1'],
networkAdminCertificateFile: [pem1],
networkAdminCertificateFile: ['admin1-pub.pem'],
networkAdminEnrollSecret: [true]
});
}).should.throw(/You cannot specify both certificate files and enrollment secrets for network administrators/);
});

it('should handle certificates', () => {
it('should handle certificate without private key', () => {
const result = CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1'],
networkAdminCertificateFile: ['admin1.pem']
networkAdminCertificateFile: ['admin1-pub.pem']
});
result.should.deep.equal([{
userName: 'admin1',
certificate: pem1
certificate: cert1
}]);
});

it('should handle certificate with private key', () => {
const result = CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1'],
networkAdminCertificateFile: ['admin1-pub.pem'],
networkAdminPrivateKeyFile: ['admin1-priv.pem']
});
result.should.deep.equal([
{ userName: 'admin1', certificate: cert1, privateKey: key1 }
]);
});

it('should handle more certificates than private keys', () => {
const result = CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1', 'admin2', 'admin3'],
networkAdminCertificateFile: ['admin1-pub.pem', 'admin2-pub.pem', 'admin3-pub.pem'],
networkAdminPrivateKeyFile: ['admin1-priv.pem', 'admin2-priv.pem']
});
result.should.deep.equal([
{ userName: 'admin1', certificate: cert1, privateKey: key1 },
{ userName: 'admin2', certificate: cert2, privateKey: key2 },
{ userName: 'admin3', certificate: cert3 }
]);
});

it('should handle secrets', () => {
const result = CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1'],
Expand All @@ -259,7 +295,7 @@ describe('composer transaction cmdutils unit tests', () => {
(() => {
CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1', 'admin2', 'admin3'],
networkAdminCertificateFile: [pem1]
networkAdminCertificateFile: ['admin1-pub.pem']
});
}).should.throw(/You must specify certificate files or enrollment secrets for all network administrators/);
});
Expand Down Expand Up @@ -311,12 +347,14 @@ describe('composer transaction cmdutils unit tests', () => {
it('should handle certificates amd file names', () => {
const result = CmdUtil.parseNetworkAdmins({
networkAdmin: ['admin1'],
networkAdminCertificateFile: ['admin1.pem'],
networkAdminCertificateFile: ['admin1-pub.pem'],
networkAdminPrivateKeyFile: ['admin1-priv.pem'],
file: ['admin1-doggo.card']
});
result.should.deep.equal([{
userName: 'admin1',
certificate: pem1,
certificate: cert1,
privateKey: key1,
file: 'admin1-doggo.card'
}]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Options:
--optionsFile, -O A file containing options that are specific to connection [string]
--networkAdmin, -A The identity name of the business network administrator [string] [required]
--networkAdminCertificateFile, -C The certificate of the business network administrator [string]
--networkAdminPrivateKeyFile, -K The private key of the business network administrator [string]
--networkAdminEnrollSecret, -S The enrollment secret for the business network administrator [string]
--card, -c The cardname to use to start the network [string] [required]
--file, -f File name of the card to be created [string]
Expand Down

0 comments on commit 8c90b3c

Please sign in to comment.