mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-26 03:25:09 +00:00
Add support for HSM based identities
Add support to the basic application to check for the HSM environment and enroll and store the certificate and HSM handle of the HSM identity in the wallet. The identity will then be retrieved form the wallet providing access for signing. Signed-off-by: Bret Harrison <beharrison@nc.rr.com>
This commit is contained in:
parent
36b5788bad
commit
d999f51dea
6 changed files with 260 additions and 62 deletions
142
asset-transfer-basic/README.md
Normal file
142
asset-transfer-basic/README.md
Normal file
|
|
@ -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 <https://dist.opendnssec.org/source/softhsm-2.2.0.tar.gz>
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
7
test-network/hsm/softhsm2.conf
Normal file
7
test-network/hsm/softhsm2.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# SoftHSM v2 configuration file
|
||||
|
||||
directories.tokendir = /tmp/
|
||||
objectstore.backend = file
|
||||
|
||||
# ERROR, WARNING, INFO, DEBUG
|
||||
log.level = INFO
|
||||
Loading…
Reference in a new issue