From 6aa3017ccbf3499d220429c577371d71926529ed Mon Sep 17 00:00:00 2001 From: Saptha Surendran <48531319+sapthasurendran@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:48:30 +0530 Subject: [PATCH] Added hsm samples using Gateway (#759) * Added hsmm Samples using Gateway Signed-off-by: sapthasurendran * Used asset-transfer-basic chaincode Signed-off-by: sapthasurendran * moved samples under asset-transfer-basic Signed-off-by: sapthasurendran * ci pipeline changes Signed-off-by: sapthasurendran * HSM config path changed Signed-off-by: sapthasurendran * added pkcs11 enabled ca Signed-off-by: sapthasurendran * HSM template added Signed-off-by: sapthasurendran * moved binaries to fabric samples bin added go mod and go sum Signed-off-by: sapthasurendran * Gopath reverrted back to localDirectory Signed-off-by: sapthasurendran * go mod added and cleanup Signed-off-by: sapthasurendran * test file directory Signed-off-by: sapthasurendran * test file directory Signed-off-by: sapthasurendran * migrate to latest gateway and go version Signed-off-by: sapthasurendran * hsm script changes Readme changes Added npm prepare Signed-off-by: sapthasurendran * moved samples out of asset-transfer-basic Signed-off-by: sapthasurendran * Name changes Signed-off-by: sapthasurendran * code refactor Signed-off-by: sapthasurendran * go vet by tag Signed-off-by: sapthasurendran * pkcs11 tag added to lint script Signed-off-by: sapthasurendran * Readme updates Signed-off-by: sapthasurendran * application-typescript code refactor Signed-off-by: sapthasurendran * readme note added Signed-off-by: sapthasurendran --- ci/azure-pipelines.yml | 9 +- ci/scripts/lint.sh | 6 +- ci/scripts/run-test-network-basic.sh | 33 ++- ci/templates/install-deps-hsm.yml | 13 ++ hardware-security-module/.gitignore | 1 + hardware-security-module/README.md | 129 +++++++++++ .../application-go/go.mod | 19 ++ .../application-go/go.sum | 143 ++++++++++++ .../application-go/hsm-sample.go | 203 ++++++++++++++++++ .../application-typescript/.eslintrc.yaml | 29 +++ .../application-typescript/.gitignore | 3 + .../application-typescript/package.json | 35 +++ .../application-typescript/src/hsm-sample.ts | 157 ++++++++++++++ .../application-typescript/tsconfig.json | 18 ++ .../ca-client-config/.gitignore | 1 + .../fabric-ca-client-config-template.yaml | 168 +++++++++++++++ .../scripts/generate-hsm-user.sh | 62 ++++++ 17 files changed, 1021 insertions(+), 8 deletions(-) create mode 100644 ci/templates/install-deps-hsm.yml create mode 100644 hardware-security-module/.gitignore create mode 100644 hardware-security-module/README.md create mode 100644 hardware-security-module/application-go/go.mod create mode 100644 hardware-security-module/application-go/go.sum create mode 100644 hardware-security-module/application-go/hsm-sample.go create mode 100644 hardware-security-module/application-typescript/.eslintrc.yaml create mode 100644 hardware-security-module/application-typescript/.gitignore create mode 100644 hardware-security-module/application-typescript/package.json create mode 100644 hardware-security-module/application-typescript/src/hsm-sample.ts create mode 100644 hardware-security-module/application-typescript/tsconfig.json create mode 100644 hardware-security-module/ca-client-config/.gitignore create mode 100644 hardware-security-module/ca-client-config/fabric-ca-client-config-template.yaml create mode 100755 hardware-security-module/scripts/generate-hsm-user.sh diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 04f9ae2b..f383347d 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -10,14 +10,16 @@ trigger: variables: - name: FABRIC_VERSION value: 2.4 + - name: GOPATH + value: $(Build.Repository.LocalPath) - name: GO_BIN - value: $(Build.Repository.LocalPath)/bin + value: $(GOPATH)/bin - name: GO_VER value: 1.18.3 - name: NODE_VER value: 16.x - name: PATH - value: $(Build.Repository.LocalPath)/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin + value: $(GOPATH)/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin - group: credentials jobs: @@ -124,8 +126,7 @@ jobs: steps: - template: templates/install-deps.yml - - script: sudo apt-get install softhsm2 - displayName: Install SoftHSM + - template: templates/install-deps-hsm.yml - script: ../ci/scripts/run-test-network-basic.sh workingDirectory: test-network displayName: Run Test Network Basic Chaincode diff --git a/ci/scripts/lint.sh b/ci/scripts/lint.sh index 5bdf2917..74b4f495 100755 --- a/ci/scripts/lint.sh +++ b/ci/scripts/lint.sh @@ -17,16 +17,16 @@ for dir in $dirs; do pushd $dir if [[ "$dir" =~ "-go" ]]; then print "Running go vet" - go vet ./... + go vet -tags pkcs11 ./... print "Running gofmt" - output=$(gofmt -l -s $(go list -f '{{.Dir}}' ./...)) + output=$(gofmt -l -s $(go list -tags pkcs11 -f '{{.Dir}}' ./...)) if [[ "${output}" != "" ]]; then print "The following files contain formatting errors, please run 'gofmt -l -w ' to fix these issues:" echo "${output}" fi print "Running goimports" - output=$(goimports -l $(go list -f '{{.Dir}}' ./...)) + output=$(goimports -l $(go list -tags pkcs11 -f '{{.Dir}}' ./...)) if [[ "${output}" != "" ]]; then print "The following files contain import errors, please run 'goimports -l -w ' to fix these issues:" echo "${output}" diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index a8931055..5194fbd0 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -93,7 +93,6 @@ print "Initializing Typescript HSM application" pushd ../asset-transfer-basic/application-typescript-hsm print "Setup SoftHSM" export SOFTHSM2_CONF=$PWD/softhsm2.conf -softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 print "install dependencies" npm install print "Building app.ts" @@ -103,6 +102,38 @@ node dist/app.js popd stopNetwork +# Run Typescript HSM gateway application +echo 'Delete fabric-ca-client from samples bin' +rm ../bin/fabric-ca-client +echo 'go install pkcs11 enabled fabric-ca-client' +go install -tags pkcs11 github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest +createNetwork +print "Initializing Typescript HSM gateway application" +pushd ../hardware-security-module/scripts/ +print "Enroll and register User in HSM" +./generate-hsm-user.sh HSMUser +pushd ../application-typescript/ +print "install dependencies and prepare for running" +npm install +print "Running the output app" +npm run start +popd +popd +stopNetwork + +# Run Go HSM gateway application +createNetwork +print "Initializing Go HSM gateway application" +pushd ../hardware-security-module/scripts/ +print "Register and enroll user in HSM" +./generate-hsm-user.sh HSMUser +pushd ../application-go +print "Running the output app" +go run -tags pkcs11 . +popd +popd +stopNetwork + # Run Go gateway application createNetwork print "Initializing Go gateway application" diff --git a/ci/templates/install-deps-hsm.yml b/ci/templates/install-deps-hsm.yml new file mode 100644 index 00000000..f802a3eb --- /dev/null +++ b/ci/templates/install-deps-hsm.yml @@ -0,0 +1,13 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +steps: + - script: | + sudo apt install -y softhsm2 + displayName: Install SoftHSM + - script: | + echo directories.tokendir = /tmp > $HOME/softhsm2.conf + export SOFTHSM2_CONF=$HOME/softhsm2.conf + softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 + displayName: Set up SoftHSM \ No newline at end of file diff --git a/hardware-security-module/.gitignore b/hardware-security-module/.gitignore new file mode 100644 index 00000000..3916f359 --- /dev/null +++ b/hardware-security-module/.gitignore @@ -0,0 +1 @@ +crypto-material \ No newline at end of file diff --git a/hardware-security-module/README.md b/hardware-security-module/README.md new file mode 100644 index 00000000..c7ddc110 --- /dev/null +++ b/hardware-security-module/README.md @@ -0,0 +1,129 @@ +# Fabric Gateway HSM Samples + +The samples show how to create client applications that invoke transactions with HSM Identities using the +new embedded Gateway in Fabric. + +The samples will only run against Fabric v2.4 and higher. + +Sample client applications are available to demonstrate the features of the Fabric Gateway and associated SDKs using this network. + +> **_NOTE:_** When you use an HSM, private keys for a Fabric enrollment are stored within a dedicated hardware module, rather than in plain text on a local file system. + +## Installations + +### C Compilers + +In order for the client application to run successfully you must ensure you have C compilers and Python 3 (Note that Python 2 may still work however Python 2 is out of support and could stop working in the future) installed otherwise the node dependency `pkcs11js` will not be built and the application will fail. The failure will have an error such as + +``` +Error: Cannot find module 'pkcs11js' +``` + +how to install the required C Compilers and Python will depend on your operating system and version. + +### SoftHSM + +In order to run the 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: `sudo apt 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.5.0.tar.gz` +4. `cd softhsm-2.5.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` + +### PKCS#11 enabled fabric-ca-client binary +To be able to register and enroll identities using an HSM you need a PKCS#11 enabled version of `fabric-ca-client` +To install this use the following command + +```bash +go install -tags 'pkcs11' github.com/hyperledger/fabric-ca/cmd/fabric-ca-client@latest +``` + +## Running the sample + +The Fabric test network is used to deploy and run this sample. Follow these steps in order: + +1. Create the test network and a channel (from the `test-network` folder). + ``` + ./network.sh up createChannel -ca + ``` + +2. Deploy one of the smart contract implementations (from the `test-network` folder). + ``` + # To deploy the TypeScript chaincode implementation + ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript + + # To deploy the Go chaincode implementation + ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go + + # To deploy the Java chaincode implementation + ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java + ``` + +## Initialize a token to store keys in SoftHSM + +If you have not initialized a token previously (or it has been deleted) then you will need to perform this one time operation + +```bash +echo directories.tokendir = /tmp > $HOME/softhsm2.conf +export SOFTHSM2_CONF=$HOME/softhsm2.conf +softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 +``` + +This will create a SoftHSM configuration file called `softhsm2.conf` and will be stored in the home directory. This is +where the sample expects to find a SoftHSM configuration file + +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. + +## Enroll the HSM User + +A user, `HSMUser`, who is HSM managed needs to be registered then enrolled for the sample. + +If your PKCS11 library (libsofthsm2.so) is not located in one of the typical Linux system locations checked by this sample's scripts and applications, you will need to explicitly specify the library location using the `PKCS11_LIB` environment variable. + +```bash +export PKCS11_LIB='' +``` +Register a user `HSMUser` with the CA in Org1 (if not already registered) and then enroll that user which will generate a certificate on the file system for use by the sample. The private key is stored in SoftHSM. + +```bash +scripts/generate-hsm-user.sh HSMUser +``` + +### Go SDK + +For HSM support you need to ensure you include the `pkcs11` build tag. + +``` +cd hardware-security-module/application-go +go run -tags pkcs11 . +``` + +### Node SDK + +``` +cd hardware-security-module/application-typescript +npm install +npm start +``` +## Cleanup + +When you are finished running the samples, the local test-network can be brought down with the following command (from the `test-network` folder): + + ``` +./network.sh down +``` \ No newline at end of file diff --git a/hardware-security-module/application-go/go.mod b/hardware-security-module/application-go/go.mod new file mode 100644 index 00000000..ba63ab72 --- /dev/null +++ b/hardware-security-module/application-go/go.mod @@ -0,0 +1,19 @@ +module github.com/hyperledger/fabric-samples/asset-transfer-basic/application-gateway-hsm/go + +go 1.18 + +require ( + github.com/hyperledger/fabric-gateway v1.1.0 + google.golang.org/grpc v1.47.0 +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/hyperledger/fabric-protos-go-apiv2 v0.0.0-20220615102044-467be1c7b2e7 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect + golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 // indirect + google.golang.org/protobuf v1.28.0 // indirect +) diff --git a/hardware-security-module/application-go/go.sum b/hardware-security-module/application-go/go.sum new file mode 100644 index 00000000..163653f3 --- /dev/null +++ b/hardware-security-module/application-go/go.sum @@ -0,0 +1,143 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hyperledger/fabric-gateway v1.1.0 h1:zQ6BjUCBCUUbPQNI/B/rzBD6QRvaqWxEIYAI6gtUZ14= +github.com/hyperledger/fabric-gateway v1.1.0/go.mod h1:A+MuROWOKhmUsYVO2PREggHLPgPAXaudwCoZRpuSeqs= +github.com/hyperledger/fabric-protos-go-apiv2 v0.0.0-20220615102044-467be1c7b2e7 h1:loYDK6Vrf7z3fff6YBVKFkFeCGCoKr8O2ed02CESBUQ= +github.com/hyperledger/fabric-protos-go-apiv2 v0.0.0-20220615102044-467be1c7b2e7/go.mod h1:smwq1q6eKByqQAp0SYdVvE1MvDoneF373j11XwWajgA= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= +golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 h1:a221mAAEAzq4Lz6ZWRkcS8ptb2mxoxYSt4N68aRyQHM= +google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hardware-security-module/application-go/hsm-sample.go b/hardware-security-module/application-go/hsm-sample.go new file mode 100644 index 00000000..f8c04c81 --- /dev/null +++ b/hardware-security-module/application-go/hsm-sample.go @@ -0,0 +1,203 @@ +//go:build pkcs11 +// +build pkcs11 + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "encoding/json" + "encoding/pem" + "errors" + "os" + + "crypto/x509" + "fmt" + "io/ioutil" + "time" + + "github.com/hyperledger/fabric-gateway/pkg/client" + "github.com/hyperledger/fabric-gateway/pkg/identity" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +const ( + mspID = "Org1MSP" + certPath = "../crypto-material/hsm/HSMUser/signcerts/cert.pem" + tlsCertPath = "../../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" + peerEndpoint = "localhost:7051" +) + +var now = time.Now() +var assetId = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6) + +func main() { + fmt.Println("Running the GO HSM Sample") + + // The gRPC client connection should be shared by all Gateway connections to this endpoint + clientConnection := newGrpcConnection() + defer clientConnection.Close() + + hsmSignerFactory, err := identity.NewHSMSignerFactory(findSoftHSMLibrary()) + if err != nil { + panic(err) + } + defer hsmSignerFactory.Dispose() + + certificatePEM, err := ioutil.ReadFile(certPath) + if err != nil { + panic(err) + } + + id := newIdentity(certificatePEM) + ski := getSKI(certificatePEM) + hsmSign, hsmSignClose := newHSMSign(hsmSignerFactory, ski) + defer hsmSignClose() + + // Create a Gateway connection for a specific client identity + gateway, err := client.Connect(id, client.WithSign(hsmSign), client.WithClientConnection(clientConnection)) + if err != nil { + panic(err) + } + defer gateway.Close() + + exampleTransaction(gateway) + fmt.Println() + fmt.Println("Go HSM Sample completed successfully") + fmt.Println() +} + +func exampleTransaction(gateway *client.Gateway) { + network := gateway.GetNetwork("mychannel") + contract := network.GetContract("basic") + + fmt.Printf("Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments \n") + + _, err := contract.SubmitTransaction("CreateAsset", assetId, "yellow", "5", "Tom", "1300") + if err != nil { + panic(fmt.Errorf("failed to submit transaction: %w", err)) + } + + fmt.Printf("*** Transaction committed successfully\n") + + fmt.Printf("Evaluate Transaction: ReadAsset, function returns asset attributes\n") + + evaluateResult, err := contract.EvaluateTransaction("ReadAsset", assetId) + if err != nil { + panic(fmt.Errorf("failed to evaluate transaction: %w", err)) + } + result := formatJSON(evaluateResult) + + fmt.Printf("*** Result:%s\n", result) +} + +// newGrpcConnection creates a gRPC connection to the Gateway server. +func newGrpcConnection() *grpc.ClientConn { + certificate, err := loadCertificate(tlsCertPath) + if err != nil { + panic(fmt.Errorf("failed to obtain commit status: %w", err)) + } + + certPool := x509.NewCertPool() + certPool.AddCert(certificate) + transportCredentials := credentials.NewClientTLSFromCert(certPool, "peer0.org1.example.com") + + connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials)) + if err != nil { + panic(fmt.Errorf("failed to evaluate transaction: %w", err)) + } + + return connection +} + +// newIdentity creates a client identity for this Gateway connection using an X.509 certificate. +func newIdentity(certificatePEM []byte) *identity.X509Identity { + cert, err := identity.CertificateFromPEM(certificatePEM) + if err != nil { + panic(err) + } + id, err := identity.NewX509Identity(mspID, cert) + if err != nil { + panic(err) + } + + return id +} + +// newHSMSign creates a function that generates a digital signature from a message digest using a private key. +func newHSMSign(h *identity.HSMSignerFactory, certPEM []byte) (identity.Sign, identity.HSMSignClose) { + opt := identity.HSMSignerOptions{ + Label: "ForFabric", + Pin: "98765432", + Identifier: string(certPEM), + } + + sign, close, err := h.NewHSMSigner(opt) + if err != nil { + panic(err) + } + + return sign, close +} + +func loadCertificate(filename string) (*x509.Certificate, error) { + certificatePEM, err := ioutil.ReadFile(filename) //#nosec G304 + if err != nil { + return nil, err + } + + return identity.CertificateFromPEM(certificatePEM) +} + +func getSKI(certPEM []byte) []byte { + block, _ := pem.Decode(certPEM) + + x590cert, _ := x509.ParseCertificate(block.Bytes) + pk := x590cert.PublicKey + + return skiForKey(pk.(*ecdsa.PublicKey)) +} + +func skiForKey(pk *ecdsa.PublicKey) []byte { + ski := sha256.Sum256(elliptic.Marshal(pk.Curve, pk.X, pk.Y)) + return ski[:] +} + +func findSoftHSMLibrary() string { + + libraryLocations := []string{ + "/usr/lib/softhsm/libsofthsm2.so", + "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so", + "/usr/local/lib/softhsm/libsofthsm2.so", + "/usr/lib/libacsp-pkcs11.so", + } + pkcs11lib := os.Getenv("PKCS11_LIB") + if pkcs11lib != "" { + libraryLocations = append(libraryLocations, pkcs11lib) + } + for _, libraryLocation := range libraryLocations { + if _, err := os.Stat(libraryLocation); !errors.Is(err, os.ErrNotExist) { + return libraryLocation + } + } + + panic("No SoftHSM library can be found. The Sample requires SoftHSM to be installed") +} + +// Format JSON data +func formatJSON(data []byte) string { + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, data, " ", ""); err != nil { + panic(fmt.Errorf("failed to parse JSON: %w", err)) + } + return prettyJSON.String() +} diff --git a/hardware-security-module/application-typescript/.eslintrc.yaml b/hardware-security-module/application-typescript/.eslintrc.yaml new file mode 100644 index 00000000..79614105 --- /dev/null +++ b/hardware-security-module/application-typescript/.eslintrc.yaml @@ -0,0 +1,29 @@ +env: + node: true + es2020: true +root: true +ignorePatterns: + - dist/ +extends: + - eslint:recommended +rules: + indent: + - error + - 4 + quotes: + - error + - single +overrides: + - files: + - "**/*.ts" + parser: "@typescript-eslint/parser" + parserOptions: + sourceType: module + ecmaFeatures: + impliedStrict: true + plugins: + - "@typescript-eslint" + extends: + - eslint:recommended + - plugin:@typescript-eslint/eslint-recommended + - plugin:@typescript-eslint/recommended diff --git a/hardware-security-module/application-typescript/.gitignore b/hardware-security-module/application-typescript/.gitignore new file mode 100644 index 00000000..1a993212 --- /dev/null +++ b/hardware-security-module/application-typescript/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +package-lock.json diff --git a/hardware-security-module/application-typescript/package.json b/hardware-security-module/application-typescript/package.json new file mode 100644 index 00000000..e35ee892 --- /dev/null +++ b/hardware-security-module/application-typescript/package.json @@ -0,0 +1,35 @@ +{ + "name": "gateway-hsm-sample", + "version": "0.0.1", + "description": "", + "main": "dist/hsm-sample.js", + "engines": { + "node": "^14.15.0 || ^16.13.0" + }, + "scripts": { + "build": "tsc", + "prepare": "npm run build", + "clean": "rimraf dist", + "lint": "eslint . --ext .ts", + "start": "SOFTHSM2_CONF=${HOME}/softhsm2.conf node dist/hsm-sample.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "~1.6.7", + "@hyperledger/fabric-gateway": "^1.1.0", + "jsrsasign": "^10.3.0" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/jsrsasign": "^9.0.3", + "@types/node": "^14.17.32", + "@typescript-eslint/eslint-plugin": "^5.3.0", + "@typescript-eslint/parser": "^5.3.0", + "eslint": "^8.1.0", + "npm-run-all": "^4.1.5", + "rimraf": "^3.0.2", + "typescript": "~4.5.4" + } +} diff --git a/hardware-security-module/application-typescript/src/hsm-sample.ts b/hardware-security-module/application-typescript/src/hsm-sample.ts new file mode 100644 index 00000000..c99fcd68 --- /dev/null +++ b/hardware-security-module/application-typescript/src/hsm-sample.ts @@ -0,0 +1,157 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as grpc from '@grpc/grpc-js'; +import * as crypto from 'crypto'; +import { connect, Gateway, HSMSigner, HSMSignerFactory, HSMSignerOptions, signers } from '@hyperledger/fabric-gateway'; +import * as fs from 'fs'; +import * as jsrsa from 'jsrsasign'; +import * as path from 'path'; +import { TextDecoder } from 'util'; + +const mspId = 'Org1MSP'; +const user = 'HSMUser'; +const assetId = `asset${Date.now()}`; +const utf8Decoder = new TextDecoder(); + +// Sample uses fabric-ca-client generated HSM identities, certificate is located in the signcerts directory +// and has been stored in a directory of the name given to the identity. + +const certPath = path.resolve(__dirname, '..', '..', 'crypto-material', 'hsm', user, 'signcerts', 'cert.pem'); + +const tlsCertPath = path.resolve('..', '..', 'test-network','organizations','peerOrganizations', 'org1.example.com', 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'); +const peerEndpoint = 'localhost:7051'; + +async function main() { + console.log('\nRunning the Node HSM sample'); + let client; + let gateway; + let hsmSignerFactory; + let hsmSigner; + + try { + // The gRPC client connection should be shared by all Gateway connections to this endpoint + client = await newGrpcConnection(); + + // get an HSMSigner Factory. You only need to do this once for the application + hsmSignerFactory = signers.newHSMSignerFactory(findSoftHSMPKCS11Lib()); + const credentials = await fs.promises.readFile(certPath); + + // Get the signer function and a close function. The close function closes the signer + // once there is no further need for it. + hsmSigner = await newHSMSigner(hsmSignerFactory, credentials.toString()); + gateway = connect({ + client, + identity: { mspId, credentials }, + signer:hsmSigner.signer, + }); + + await exampleTransaction(gateway); + console.log(); + console.log('Node HSM sample completed successfully'); + } finally { + gateway?.close(); + client?.close(); + hsmSignerFactory?.dispose(); + // close the HSM Signer + hsmSigner?.close(); + } +} + +async function exampleTransaction(gateway: Gateway):Promise { + const network = gateway.getNetwork('mychannel'); + const contract = network.getContract('basic'); + + console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments'); + + await contract.submitTransaction( + 'CreateAsset', + assetId, + 'yellow', + '5', + 'Tom', + '1300', + ); + + console.log('*** Transaction committed successfully'); + + console.log('\n--> Evaluate Transaction: ReadAsset, function returns asset attributes'); + + const resultBytes = await contract.evaluateTransaction('ReadAsset', assetId); + + const resultJson = utf8Decoder.decode(resultBytes); + const result = JSON.parse(resultJson); + console.log('*** Result:', result); +} + +async function newGrpcConnection(): Promise { + const tlsRootCert = await fs.promises.readFile(tlsCertPath); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + + return new grpc.Client(peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': 'peer0.org1.example.com' + }); +} + +// Create a new HSM Signer +async function newHSMSigner(hsmSignerFactory: HSMSignerFactory, certificatePEM: string): Promise { + const ski = getSKIFromCertificate(certificatePEM); + + // Options for the signer based on using SoftHSM with Token initialized as follows + // softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 + const hsmSignerOptions: HSMSignerOptions = { + label: 'ForFabric', + pin: '98765432', + identifier: ski + } + return hsmSignerFactory.newSigner(hsmSignerOptions); +} + +// Utility to find the SoftHSM PKCS11 library as it's location can vary based on +// operating system and version +function findSoftHSMPKCS11Lib(): string { + const commonSoftHSMPathNames = [ + '/usr/lib/softhsm/libsofthsm2.so', + '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so', + '/usr/local/lib/softhsm/libsofthsm2.so', + '/usr/lib/libacsp-pkcs11.so', + ]; + const pkcs11lib = process.env['PKCS11_LIB']; + if (pkcs11lib) { + commonSoftHSMPathNames.push(pkcs11lib); + } + for (const pathnameToTry of commonSoftHSMPathNames) { + if (fs.existsSync(pathnameToTry)) { + return pathnameToTry + } + } + + throw new Error('Unable to find PKCS11 library') +} + +// fabric-ca-client set's the CKA_ID of the public/private keys in the HSM to a generated SKI +// value. This function replicates that calculation from a certificate PEM so that the HSM +// object associated with the certificate can be found +function getSKIFromCertificate(certificatePEM: string): Buffer { + const key = jsrsa.KEYUTIL.getKey(certificatePEM); + const uncompressedPoint = getUncompressedPointOnCurve(key as jsrsa.KJUR.crypto.ECDSA); + const hashBuffer = crypto.createHash('sha256'); + hashBuffer.update(uncompressedPoint); + + const digest = hashBuffer.digest('hex'); + return Buffer.from(digest, 'hex'); +} + +function getUncompressedPointOnCurve(key: jsrsa.KJUR.crypto.ECDSA): Buffer { + const xyhex = key.getPublicKeyXYHex(); + const xBuffer = Buffer.from(xyhex.x, 'hex'); + const yBuffer = Buffer.from(xyhex.y, 'hex'); + const uncompressedPrefix = Buffer.from('04', 'hex'); + const uncompressedPoint = Buffer.concat([uncompressedPrefix, xBuffer, yBuffer]); + return uncompressedPoint; +} + +main().catch(console.error); diff --git a/hardware-security-module/application-typescript/tsconfig.json b/hardware-security-module/application-typescript/tsconfig.json new file mode 100644 index 00000000..ce5ff5ab --- /dev/null +++ b/hardware-security-module/application-typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/node14/tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "src/" + ] +} diff --git a/hardware-security-module/ca-client-config/.gitignore b/hardware-security-module/ca-client-config/.gitignore new file mode 100644 index 00000000..09146f84 --- /dev/null +++ b/hardware-security-module/ca-client-config/.gitignore @@ -0,0 +1 @@ +fabric-ca-client-config.yaml \ No newline at end of file diff --git a/hardware-security-module/ca-client-config/fabric-ca-client-config-template.yaml b/hardware-security-module/ca-client-config/fabric-ca-client-config-template.yaml new file mode 100644 index 00000000..b721fbe8 --- /dev/null +++ b/hardware-security-module/ca-client-config/fabric-ca-client-config-template.yaml @@ -0,0 +1,168 @@ + +############################################################################# +# This is a configuration file for the fabric-ca-client command. +# +# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES +# ------------------------------------------------ +# Each configuration element can be overridden via command line +# arguments or environment variables. The precedence for determining +# the value of each element is as follows: +# 1) command line argument +# Examples: +# a) --url https://localhost:7054 +# To set the fabric-ca server url +# b) --tls.client.certfile certfile.pem +# To set the client certificate for TLS +# 2) environment variable +# Examples: +# a) FABRIC_CA_CLIENT_URL=https://localhost:7054 +# To set the fabric-ca server url +# b) FABRIC_CA_CLIENT_TLS_CLIENT_CERTFILE=certfile.pem +# To set the client certificate for TLS +# 3) configuration file +# 4) default value (if there is one) +# All default values are shown beside each element below. +# +# FILE NAME ELEMENTS +# ------------------ +# The value of all fields whose name ends with "file" or "files" are +# name or names of other files. +# For example, see "tls.certfiles" and "tls.client.certfile". +# The value of each of these fields can be a simple filename, a +# relative path, or an absolute path. If the value is not an +# absolute path, it is interpreted as being relative to the location +# of this configuration file. +# +############################################################################# + +############################################################################# +# Client Configuration +############################################################################# + +# URL of the Fabric-ca-server (default: http://localhost:7054) +url: http://localhost:7054 + +# Membership Service Provider (MSP) directory +# This is useful when the client is used to enroll a peer or orderer, so +# that the enrollment artifacts are stored in the format expected by MSP. +mspdir: caadmin + +############################################################################# +# TLS section for secure socket connection +# +# certfiles - PEM-encoded list of trusted root certificate files +# client: +# certfile - PEM-encoded certificate file for when client authentication +# is enabled on server +# keyfile - PEM-encoded key file for when client authentication +# is enabled on server +############################################################################# +tls: + # TLS section for secure socket connection + certfiles: + client: + certfile: + keyfile: + +############################################################################# +# Certificate Signing Request section for generating the CSR for an +# enrollment certificate (ECert) +# +# cn - Used by CAs to determine which domain the certificate is to be generated for +# +# keyrequest - Properties to use when generating a private key. +# algo - key generation algorithm to use +# size - size of key to generate +# reusekey - reuse existing key during reenrollment +# +# serialnumber - The serialnumber field, if specified, becomes part of the issued +# certificate's DN (Distinguished Name). For example, one use case for this is +# a company with its own CA (Certificate Authority) which issues certificates +# to its employees and wants to include the employee's serial number in the DN +# of its issued certificates. +# WARNING: The serialnumber field should not be confused with the certificate's +# serial number which is set by the CA but is not a component of the +# certificate's DN. +# +# names - A list of name objects. Each name object should contain at least one +# "C", "L", "O", or "ST" value (or any combination of these) where these +# are abbreviations for the following: +# "C": country +# "L": locality or municipality (such as city or town name) +# "O": organization +# "OU": organizational unit, such as the department responsible for owning the key; +# it can also be used for a "Doing Business As" (DBS) name +# "ST": the state or province +# +# Note that the "OU" or organizational units of an ECert are always set according +# to the values of the identities type and affiliation. OUs are calculated for an enroll +# as OU=, OU=, ..., OU=. For example, an identity +# of type "client" with an affiliation of "org1.dept2.team3" would have the following +# organizational units: OU=client, OU=org1, OU=dept2, OU=team3 +# +# hosts - A list of host names for which the certificate should be valid +# +############################################################################# +csr: + cn: admin + keyrequest: + algo: ecdsa + size: 256 + reusekey: false + serialnumber: + names: + - C: US + ST: North Carolina + L: + O: Hyperledger + OU: Fabric + hosts: + - tryfabric + +############################################################################# +# Registration section used to register a new identity with fabric-ca server +# +# name - Unique name of the identity +# type - Type of identity being registered (e.g. 'peer, app, user') +# affiliation - The identity's affiliation +# maxenrollments - The maximum number of times the secret can be reused to enroll. +# Specially, -1 means unlimited; 0 means to use CA's max enrollment +# value. +# attributes - List of name/value pairs of attribute for identity +############################################################################# +id: + name: + type: + affiliation: + maxenrollments: 0 + attributes: + # - name: + # value: + +############################################################################# +# Enrollment section used to enroll an identity with fabric-ca server +# +# profile - Name of the signing profile to use in issuing the certificate +# label - Label to use in HSM operations +############################################################################# +enrollment: + profile: + label: + +############################################################################# +# Name of the CA to connect to within the fabric-ca server +############################################################################# +caname: + +############################################################################# +# BCCSP (BlockChain Crypto Service Provider) section allows to select which +# crypto implementation library to use +############################################################################# +bccsp: + default: PKCS11 + PKCS11: + Library: REPLACE_ME_HSMLIB + Pin: 98765432 + Label: ForFabric + hash: SHA2 + security: 256 \ No newline at end of file diff --git a/hardware-security-module/scripts/generate-hsm-user.sh b/hardware-security-module/scripts/generate-hsm-user.sh new file mode 100755 index 00000000..52f123b4 --- /dev/null +++ b/hardware-security-module/scripts/generate-hsm-user.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -eo pipefail +# script directory +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# define the CA setup +CA_HOST=localhost +CA_URL=${CA_HOST}:7054 + +TLS_CERT="${SCRIPT_DIR}/../../test-network/organizations/fabric-ca/org1/tls-cert.pem" + +LocateHsmLib() { + if [[ -n "${PKCS11_LIB}" && -f "${PKCS11_LIB}" ]]; then + echo "${PKCS11_LIB}" + return + fi + + local POSSIBLE_LIB_LOC=( \ + '/usr/lib/softhsm/libsofthsm2.so' \ + '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so' \ + '/usr/local/lib/softhsm/libsofthsm2.so' \ + '/usr/lib/libacsp-pkcs11.so' \ + ) + for TEST_LIB in "${POSSIBLE_LIB_LOC[@]}"; do + if [ -f "${TEST_LIB}" ]; then + echo "${TEST_LIB}" + return + fi + done +} + +HSM2_LIB=$(LocateHsmLib) +[ -z "$HSM2_LIB" ] && echo No SoftHSM PKCS11 Library found, ensure you have installed softhsm2 && exit 1 + +# create a softhsm2.conf file if one doesn't exist +HSM2_CONF=$HOME/softhsm2.conf +[ ! -f "$HSM2_CONF" ] && echo directories.tokendir = /tmp > "$HSM2_CONF" + +# Update the client config file to point to the softhsm pkcs11 library +# which must be in $HOME/softhsm directory + +CLIENT_CONFIG_TEMPLATE=${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config-template.yaml +CLIENT_CONFIG=${SCRIPT_DIR}/../ca-client-config/fabric-ca-client-config.yaml +cp $CLIENT_CONFIG_TEMPLATE $CLIENT_CONFIG + +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' s+REPLACE_ME_HSMLIB+"${HSM2_LIB}"+g $CLIENT_CONFIG +else + sed -i s+REPLACE_ME_HSMLIB+"${HSM2_LIB}"+g $CLIENT_CONFIG +fi + +# create the users, remove any existing users +CRYPTO_PATH=$SCRIPT_DIR/../crypto-material/hsm +[ -d "$CRYPTO_PATH" ] && rm -fr "$CRYPTO_PATH" + +# user passed in as parameter +CAADMIN="admin" +CAADMIN_PW="adminpw" +HSMUSER=$1 + +SOFTHSM2_CONF=$HSM2_CONF fabric-ca-client enroll -c $CLIENT_CONFIG -u https://$CAADMIN:$CAADMIN_PW@$CA_URL --mspdir "$CRYPTO_PATH"/$CAADMIN --csr.hosts example.com --tls.certfiles "${TLS_CERT}" +! SOFTHSM2_CONF=$HSM2_CONF fabric-ca-client register -c $CLIENT_CONFIG --mspdir "$CRYPTO_PATH"/$CAADMIN --id.name "$HSMUSER" --id.secret "$HSMUSER" --id.type client --caname ca-org1 --id.maxenrollments 0 -m example.com -u https://$CA_URL --tls.certfiles "${TLS_CERT}" && echo user probably already registered, continuing +SOFTHSM2_CONF=$HSM2_CONF fabric-ca-client enroll -c $CLIENT_CONFIG -u https://"$HSMUSER":"$HSMUSER"@$CA_URL --mspdir "$CRYPTO_PATH"/"$HSMUSER" --csr.hosts example.com --tls.certfiles "${TLS_CERT}" \ No newline at end of file