diff --git a/asset-transfer-basic/README.md b/asset-transfer-basic/README.md new file mode 100644 index 00000000..5e484c0e --- /dev/null +++ b/asset-transfer-basic/README.md @@ -0,0 +1,142 @@ +# Asset Transfer Basic javascript Sample + +The asset transfer basic sample demonstrates submitting transactions +and queries. + + +## About the Sample + +This javascript sample application is able to use any of the asset transfer basic +chaincode samples. It will show how to enroll an admin identity. The admin will then +register and enroll a user identity to work with the Fabric network. +The application will demostrate the basic operations of create, update, delete, +and query on an asset. + + +## Running the sample + +Use the Fabric test network utility (`network.sh`) to start a Fabric network and deploy one +one of the sample chaincodes. + +Follow these step in order. +- Start the test network with a Certificate Authority and create a channel +``` +cd test-network +./network.sh up createChannel -c mychannel -ca +``` + +- Deploy the chaincode (smart contract) +``` +# to deploy the javascript version +./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl javascript -ccp ./../asset-transfer-basic/chaincode-javascript/ -ccn basic +``` + +- Run the application +``` +cd asset-transfer-basic/application-javascript +npm install +node app.js +``` + +If you see an error: +``` + 2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied + ******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied +``` + or +``` + Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] + ******** FAILED to run the application: Error: Identity not found in wallet: appUser +``` +Delete the `/fabric-samples/asset-transfer-basic/application-javascript/wallet` directory +and retry this application. + +The certificate authority must have been restarted and the saved certificates for the +admin and application user are not valid. Deleting the wallet store will force these to be reset +with the new certificate authority. + +To see the SDK workings, try setting the logging to show on the console before running +``` +export HFC_LOGGING='{"debug":"console"}' +``` +or log to a file +``` +export HFC_LOGGING='{"debug":"./debug.log"}' +``` + +## Clean up +When you are finished, you can bring down the test network. +The command will remove all the nodes of the test network, and delete any +ledger data that you created: + +``` +./network.sh down +``` +Be sure to delete the wallet directory create by the application. +The stored identities will no longer be valid when restarting the network. + +## Configuring and running a Hardware Security Module + +The javascript sample application for asset transfer basic includes +support for a HSM. Below are the steps required to run a Hardware Security Module (HSM) +simulator locally. + +**_Note:_** + When using the HSM simulator you will only be able to run the application once + without restarting the Fabric network and removing the wallet store. + Rerunning the application will restart the HSM simulator. The user's identity + information that has been saved to the wallet store will not exist in the + restarted HSM simulator. + +### Install SoftHSM + +In order to run the javascript application in the absence of a real HSM, a software +emulator of the PKCS#11 interface is required. +For more information please refer to [SoftHSM](https://www.opendnssec.org/softhsm/). + +SoftHSM can either be installed using the package manager for your host system: + +* Ubuntu: `apt-get install softhsm2` +* macOS: `brew install softhsm` +* Windows: **unsupported** + +Or compiled and installed from source: + +1. install openssl 1.0.0+ or botan 1.10.0+ +2. download the source code from +3. `tar -xvf softhsm-2.2.0.tar.gz` +4. `cd softhsm-2.2.0` +5. `./configure --disable-gost` (would require additional libraries, turn it off unless you need 'gost' algorithm support for the Russian market) +6. `make` +7. `sudo make install` + +### Specify the SoftHSM configuration file + +The configuration file required for the simulator has been created and is +located in the test network directory. Set the following environment +variable to point to a config file before running the javascript application. +When the javascript application sees the following environment variable, it will +attempt to connect to the HSM simulator. + +```bash +export SOFTHSM2_CONF="../test-network/hsm/softhsm2.conf" +``` + +### Create a token to store keys in the HSM + +```bash +softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 +``` + +The Security Officer PIN, specified with the `--so-pin` flag, can be used to re-initialize the token, +and the user PIN (see below), specified with the `--pin` flag, is used by applications to access the token for +generating and retrieving keys. + +### Configuration + +By default the javascript sample application will run with SoftHSM using slot `0` and user PIN `98765432`. +If your configuration is different, edit the application or use these environment variables to pass in the values: + +* PKCS11_LIB - path to the SoftHSM2 library; if not specified, the application will search a list of common install locations +* PKCS11_PIN +* PKCS11_SLOT \ No newline at end of file diff --git a/asset-transfer-basic/application-javascript/app.js b/asset-transfer-basic/application-javascript/app.js index 7f368c61..06d140c6 100644 --- a/asset-transfer-basic/application-javascript/app.js +++ b/asset-transfer-basic/application-javascript/app.js @@ -6,77 +6,43 @@ 'use strict'; -const { Gateway, Wallets } = require('fabric-network'); +/** + * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes + * -- How to submit a transaction + * -- How to query and check the + * -- How to use an HSM for users key and signing support + **/ + +const { Gateway, Wallets, HsmX509Provider } = require('fabric-network'); const FabricCAServices = require('fabric-ca-client'); const path = require('path'); -const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js'); +const fs = require('fs'); +const { buildCAClient, registerAndEnrollUser, enrollAdmin, getHSMLibPath} = require('../../test-application/javascript/CAUtil.js'); const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js'); const channelName = 'mychannel'; const chaincodeName = 'basic'; const mspOrg1 = 'Org1MSP'; const walletPath = path.join(__dirname, 'wallet'); -const org1UserId = 'appUser'; + +const user1 = 'appUser1'; +const user2 = 'appUser2'; function prettyJSONString(inputString) { return JSON.stringify(JSON.parse(inputString), null, 2); } -// pre-requisites: -// - fabric-sample two organization test-network setup with two peers, ordering service, -// and 2 certificate authorities -// ===> from directory /fabric-samples/test-network -// ./network.sh up createChannel -ca -// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel" -// with the chaincode name of "basic". The following deploy command will package, -// install, approve, and commit the javascript chaincode, all the actions it takes -// to deploy a chaincode to a channel. -// ===> from directory /fabric-samples/test-network -// ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript -// - Be sure that node.js is installed -// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript -// node -v -// - npm installed code dependencies -// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript -// npm install -// - to run this test application -// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript -// node app.js - -// NOTE: If you see kind an error like these: -/* - 2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied - ******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied - - OR - - Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] - ******** FAILED to run the application: Error: Identity not found in wallet: appUser -*/ -// Delete the /fabric-samples/asset-transfer-basic/application-javascript/wallet directory -// and retry this application. -// -// The certificate authority must have been restarted and the saved certificates for the -// admin and application user are not valid. Deleting the wallet store will force these to be reset -// with the new certificate authority. -// - -/** - * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes - * -- How to submit a transaction - * -- How to query and check the results - * - * To see the SDK workings, try setting the logging to show on the console before running - * export HFC_LOGGING='{"debug":"console"}' - */ async function main() { try { + let username = user1; + // build an in memory object with the network configuration (also known as a connection profile) const ccp = buildCCPOrg1(); // build an instance of the fabric ca services client based on // the information in the network configuration const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com'); + let hsmCaClient; // must a different ca client with a HSM crypto suite assigned when enrolling HSM user // setup the wallet to hold the credentials of the application user const wallet = await buildWallet(Wallets, walletPath); @@ -84,9 +50,37 @@ async function main() { // in a real application this would be done on an administrative flow, and only once await enrollAdmin(caClient, wallet, mspOrg1); + // users will be created using the HSM if SOFTHSM2_CONF and the HSM simulator has been initialized + // Be sure to run the following before starting this application if you wish use the HSM simulator + /* + export SOFTHSM2_CONF="../../test-network/hsm/softhsm2.conf" + softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 + */ + + /* + NOTE: You will not be able to re run this application with the same user name when using + the HSM simulator. The simulator has been restarted and will not have the handle + as saved in the "wallet" store. + */ + if (process.env.SOFTHSM2_CONF) { + // setup the wallet provider and the ca client to have the pkcs11 cryptosuite to work with + // the HSM to generate keys and sign transactions + const hsmOptions = { + lib: getHSMLibPath(fs), + pin: process.env.PKCS11_PIN || '98765432', + slot: Number(process.env.PKCS11_SLOT || '0') + }; + // build the wallet identity provider and the PKCS11 crypto suite based on the HSM options + const hsmProvider = new HsmX509Provider(hsmOptions); + wallet.getProviderRegistry().addProvider(hsmProvider); + hsmCaClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com', hsmProvider.getCryptoSuite()); + username = user2; + } + // in a real application this would be done only when a new user was required to be added // and would be part of an administrative flow - await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1'); + // note: when not using an HSM, hsmCaClient will be 'undefined' + await registerAndEnrollUser(caClient, wallet, mspOrg1, username, 'org1.department1', hsmCaClient); // Create a new gateway instance for interacting with the fabric network. // In a real application this would be done as the backend server session is setup for @@ -100,12 +94,15 @@ async function main() { // signed by this user using the credentials stored in the wallet. await gateway.connect(ccp, { wallet, - identity: org1UserId, + identity: username, discovery: { enabled: true, asLocalhost: true } // using asLocalhost as this gateway is using a fabric network deployed locally }); // Build a network instance based on the channel where the smart contract is deployed const network = await gateway.getNetwork(channelName); + // show the peers that network (channel) knows about + // This will show all discovered peers and also peers that are in the connection profile + console.log('\n--> Network has been discovered ' + network.getChannel().getEndorsers()); // Get the contract from the network. const contract = network.getContract(chaincodeName); @@ -172,6 +169,9 @@ async function main() { console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes'); result = await contract.evaluateTransaction('ReadAsset', 'asset1'); console.log(`*** Result: ${prettyJSONString(result.toString())}`); + } catch (runError) { + console.log('**** Error:' + runError); + console.log(runError.stack); } finally { // Disconnect from the gateway when the application is closing // This will close all connections to the network diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 5d314a8f..433178c6 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -169,6 +169,8 @@ jobs: CHAINCODE_LANGUAGE: typescript steps: - template: templates/install-deps.yml + - script: sudo apt-get install softhsm2 + displayName: Install SoftHSM - script: ../ci/scripts/run-test-network-basic.sh workingDirectory: test-network displayName: Run Test Network Basic Chaincode diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index e6e38d66..a4e3b37b 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -48,6 +48,11 @@ pushd ../asset-transfer-basic/application-javascript npm install print "Executing app.js" node app.js +print "Setup the HSM simulator" +export SOFTHSM2_CONF="../../test-network/hsm/softhsm2.conf" +softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 +print "Executing app.js using the HSM simulator" +node app.js popd stopNetwork diff --git a/test-application/javascript/CAUtil.js b/test-application/javascript/CAUtil.js index 10ec7344..6cec66b2 100644 --- a/test-application/javascript/CAUtil.js +++ b/test-application/javascript/CAUtil.js @@ -6,6 +6,10 @@ 'use strict'; + // provider types +const HSM_PROVIDER = 'HSM-X.509'; +const X509_PROVIDER = 'X.509'; + const adminUserId = 'admin'; const adminUserPasswd = 'adminpw'; @@ -14,11 +18,11 @@ const adminUserPasswd = 'adminpw'; * @param {*} FabricCAServices * @param {*} ccp */ -exports.buildCAClient = (FabricCAServices, ccp, caHostName) => { +exports.buildCAClient = (FabricCAServices, ccp, caHostName, cryptoSuite) => { // Create a new CA client for interacting with the CA. const caInfo = ccp.certificateAuthorities[caHostName]; //lookup CA details from config const caTLSCACerts = caInfo.tlsCACerts.pem; - const caClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName); + const caClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName, cryptoSuite); console.log(`Built a CA Client named ${caInfo.caName}`); return caClient; @@ -41,7 +45,7 @@ exports.enrollAdmin = async (caClient, wallet, orgMspId) => { privateKey: enrollment.key.toBytes(), }, mspId: orgMspId, - type: 'X.509', + type: X509_PROVIDER, }; await wallet.put(adminUserId, x509Identity); console.log('Successfully enrolled admin user and imported it into the wallet'); @@ -50,7 +54,7 @@ exports.enrollAdmin = async (caClient, wallet, orgMspId) => { } }; -exports.registerAndEnrollUser = async (caClient, wallet, orgMspId, userId, affiliation) => { +exports.registerAndEnrollUser = async (nonHSMcaClient, wallet, orgMspId, userId, affiliation, hsmCaClient) => { try { // Check to see if we've already enrolled the user const userIdentity = await wallet.get(userId); @@ -68,11 +72,22 @@ exports.registerAndEnrollUser = async (caClient, wallet, orgMspId, userId, affil } // build a user object for authenticating with the CA - const provider = wallet.getProviderRegistry().getProvider(adminIdentity.type); + const provider = wallet.getProviderRegistry().getProvider(X509_PROVIDER); const adminUser = await provider.getUserContext(adminIdentity, adminUserId); // Register the user, enroll the user, and import the new identity into the wallet. // if affiliation is specified by client, the affiliation value must be configured in CA + let caClient = nonHSMcaClient; + let type = X509_PROVIDER; + + if (hsmCaClient) { + // Will use the HSM CA client which has been initialized with a pkcs11 crypto suite + // to work the HSM for generating keys and signing. + caClient = hsmCaClient; + type = HSM_PROVIDER; + console.log(' ---> Using HSM identity'); + } + const secret = await caClient.register({ affiliation: affiliation, enrollmentID: userId, @@ -82,17 +97,44 @@ exports.registerAndEnrollUser = async (caClient, wallet, orgMspId, userId, affil enrollmentID: userId, enrollmentSecret: secret }); + const x509Identity = { + mspId: orgMspId, + type: type, credentials: { certificate: enrollment.certificate, - privateKey: enrollment.key.toBytes(), - }, - mspId: orgMspId, - type: 'X.509', - }; + privateKey: enrollment.key.toBytes() + } + } + await wallet.put(userId, x509Identity); console.log(`Successfully registered and enrolled user ${userId} and imported it into the wallet`); } catch (error) { console.error(`Failed to register user : ${error}`); } }; + +exports.getHSMLibPath = (fs) => { + const pathnames = [ + '/usr/lib/softhsm/libsofthsm2.so', // Ubuntu + '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so', // Ubuntu apt-get install + '/usr/local/lib/softhsm/libsofthsm2.so', // Ubuntu, OSX (tar ball install) + '/usr/lib/libacsp-pkcs11.so' // LinuxOne + ]; + let pkcsLibPath = 'NOT FOUND'; + if (typeof process.env.PKCS11_LIB === 'string' && process.env.PKCS11_LIB !== '') { + pkcsLibPath = process.env.PKCS11_LIB; + } else { + // + // Check common locations for PKCS library + // + for (let i = 0; i < pathnames.length; i++) { + if (fs.existsSync(pathnames[i])) { + pkcsLibPath = pathnames[i]; + break; + } + } + } + + return pkcsLibPath; +} diff --git a/test-network/hsm/softhsm2.conf b/test-network/hsm/softhsm2.conf new file mode 100644 index 00000000..64f12c32 --- /dev/null +++ b/test-network/hsm/softhsm2.conf @@ -0,0 +1,7 @@ +# SoftHSM v2 configuration file + +directories.tokendir = /tmp/ +objectstore.backend = file + +# ERROR, WARNING, INFO, DEBUG +log.level = INFO