Skip to content

Commit

Permalink
refactor(cmd-socketio-server): remove code duplication
Browse files Browse the repository at this point in the history
- Moved verifyValidatorJwt to `cactus-common` to keep encrypt/decrypt logic
  of socketio-based validators in one, common location.
- Move config-reading and signMessageJwt helper functions from validators to cmd-socketio-server
  to remove code duplication.
  Refactor validators to use these common instead of own implementation.
- Remove ValidatorAuthentication.ts that is not used anymore
  (not part of public interface, it was copied by validators during before couple commits ago).
- Create jwt-message-authentication.test.ts from old, similar one in cactus-api-client.
- Updated readme with instructions of how to start asset-trade and electricity-trade samples
  without docker-compose (to be used during development).
  Added helper script for patching the config.

Signed-off-by: Michal Bajer <michal.bajer@fujitsu.com>
  • Loading branch information
outSH committed May 26, 2022
1 parent 4ec5b88 commit 200a7da
Show file tree
Hide file tree
Showing 38 changed files with 480 additions and 444 deletions.
11 changes: 11 additions & 0 deletions examples/cactus-example-discounted-asset-trade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ Alice will use credentials and other Indy formats such as schema and definition
cactus-example-discounted-asset-trade-blp | [2022-01-31T16:00:56.208] [INFO] www - listening on *: 5034
```

### Dockerless run
For development purposes, it might be useful to run the sample application outside of docker-compose environment.

1. Configure cactus and start the ledgers as described above.
1. Run `./script-dockerless-config-patch.sh` from `cactus-example-discounted-asset-trade/` directory. This will patch the configs and copy it to global location.
1. Start validators (each in separate cmd window).
1. `cd packages/cactus-plugin-ledger-connector-fabric-socketio/ && npm run start`
1. `cd packages/cactus-plugin-ledger-connector-go-ethereum-socketio/ && npm run start`
1. `docker build packages-python/cactus_validator_socketio_indy/ -t indy-validator && docker run -v/etc/cactus/:/etc/cactus --rm --net="indy-testnet_indy_net" -p 10080:8000 indy-validator`
1. Start asset-trade: `npm run start-dockerless`

## How to use this application

1. (Optional) Check the balance on Ethereum and the asset ownership on Fabric using the following script:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"private": true,
"scripts": {
"start": "npm run build_pip_indy_package && docker-compose build && docker-compose up",
"start-dockerless": "node ./dist/www.js",
"build": "npm run build-ts && npm run build:dev:backend:postbuild",
"build-ts": "tsc",
"build_pip_indy_package": "cd ../../packages-python/cactus_validator_socketio_indy && python3 setup.py bdist_wheel",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright 2020-2022 Hyperledger Cactus Contributors
# SPDX-License-Identifier: Apache-2.0

COMMON_CACTUS_CONFIG="/etc/cactus/"

echo "Note - script must executed from within cactus-example-discounted-asset-trade directory!"

echo "Copy local cactus config to common location ($COMMON_CACTUS_CONFIG)"
sudo rm -rf "$COMMON_CACTUS_CONFIG"
sudo cp -ar "./etc/cactus" "/etc"
sudo chown -hR $(whoami) "$COMMON_CACTUS_CONFIG"

echo "Patch validators..."
sed -i 's/asset_trade_faio2x_testnet/localhost/g' "${COMMON_CACTUS_CONFIG}/connector-fabric-socketio/default.yaml"
sed -i 's/geth1/localhost/g' "${COMMON_CACTUS_CONFIG}/connector-go-ethereum-socketio/default.yaml"

echo "Patch validator-registry-config.yaml..."
sed -i 's/ethereum-validator/localhost/g' "${COMMON_CACTUS_CONFIG}/validator-registry-config.yaml"
sed -i 's/fabric-socketio-validator/localhost/g' "${COMMON_CACTUS_CONFIG}/validator-registry-config.yaml"
sed -i 's/indy-validator-nginx/localhost/g' "${COMMON_CACTUS_CONFIG}/validator-registry-config.yaml"

echo "Patch path to asset-trade modules."
current_pwd=$(pwd)
escaped_pwd=${current_pwd//\//\\/}
sed -i "s/\/root\/cactus/$escaped_pwd/g" "${COMMON_CACTUS_CONFIG}/usersetting.yaml"

echo "Done."
10 changes: 10 additions & 0 deletions examples/cactus-example-electricity-trade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ In this example, we use the Sawtooth intkey transaction processor as an applicat
cactus-example-electricity-trade-blp | [2022-02-14T15:47:47.399] [INFO] www - listening on *: 5034
```

### Dockerless run
For development purposes, it might be useful to run the sample application outside of docker-compose environment.

1. Configure cactus and start the ledgers as described above.
1. Run `./script-dockerless-config-patch.sh` from `cactus-example-discounted-asset-trade/` directory. This will patch the configs and copy it to global location.
1. Start validators (each in separate cmd window).
1. `cd packages/cactus-plugin-ledger-connector-go-ethereum-socketio/ && npm run start`
1. `cd packages/cactus-plugin-ledger-connector-sawtooth-socketio/ && npm run start`
1. Start electricity-trade: `npm run start-dockerless`

## How to use this application
- Source account on Ethereum: `06fc56347d91c6ad2dae0c3ba38eb12ab0d72e97`
- The privkey of source account on Ethereum: `cb5d48d371916a4ea1627189d8af4f642a5d72746a06b559780c3f5932658207`
Expand Down
1 change: 1 addition & 0 deletions examples/cactus-example-electricity-trade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"private": true,
"scripts": {
"start": "docker-compose build && docker-compose up",
"start-dockerless": "node ./dist/www.js",
"build": "npm run build-ts && npm run build:dev:backend:postbuild",
"build-ts": "tsc",
"build:dev:backend:postbuild": "cp -f ../../yarn.lock ./dist/"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Copyright 2020-2022 Hyperledger Cactus Contributors
# SPDX-License-Identifier: Apache-2.0

COMMON_CACTUS_CONFIG="/etc/cactus/"

echo "Note - script must executed from within cactus-example-electricity-trade directory!"

echo "Copy local cactus config to common location ($COMMON_CACTUS_CONFIG)"
sudo rm -rf "$COMMON_CACTUS_CONFIG"
sudo cp -ar "./etc/cactus" "/etc"
sudo chown -hR $(whoami) "$COMMON_CACTUS_CONFIG"

echo "Patch validators..."
sed -i 's/geth1/localhost/g' "${COMMON_CACTUS_CONFIG}/connector-go-ethereum-socketio/default.yaml"
sed -i 's/rest-api/localhost/g' "${COMMON_CACTUS_CONFIG}/connector-sawtooth-socketio/default.yaml"

echo "Patch validator-registry-config.yaml..."
sed -i 's/ethereum-validator/localhost/g' "${COMMON_CACTUS_CONFIG}/validator-registry-config.yaml"
sed -i 's/sawtooth-validator/localhost/g' "${COMMON_CACTUS_CONFIG}/validator-registry-config.yaml"

echo "Patch path to electricity-trade modules."
current_pwd=$(pwd)
escaped_pwd=${current_pwd//\//\\/}
sed -i "s/\/root\/cactus/$escaped_pwd/g" "${COMMON_CACTUS_CONFIG}/usersetting.yaml"

echo "Done."
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,14 @@ import {
LoggerProvider,
} from "@hyperledger/cactus-common";
import { ISocketApiClient } from "@hyperledger/cactus-core-api";

import { verifyValidatorMessageJwt } from "@hyperledger/cactus-common";
import { Socket, SocketOptions, ManagerOptions, io } from "socket.io-client";
import { readFileSync } from "fs";
import { resolve as resolvePath } from "path";
import { verify, VerifyOptions, VerifyErrors, JwtPayload } from "jsonwebtoken";
import { JwtPayload } from "jsonwebtoken";
import { Observable, ReplaySubject } from "rxjs";
import { finalize } from "rxjs/operators";

/**
* Default logic for validating responses from socketio connector (validator).
* Assumes that message is JWT signed with validator private key.
* @param publicKey - Validator public key.
* @param targetData - Signed JWT message to be decoded.
* @returns Promise resolving to decoded JwtPayload.
*/
export function verifyValidatorJwt(
publicKey: string,
targetData: string,
): Promise<JwtPayload> {
return new Promise((resolve, reject) => {
const option: VerifyOptions = {
algorithms: ["ES256", "ES384", "ES512", "RS256", "RS384", "RS512"],
};

verify(
targetData,
publicKey,
option,
(err: VerifyErrors | null, decoded: JwtPayload | undefined) => {
if (err) {
reject(err);
} else if (decoded === undefined) {
reject(Error("Decoded message is undefined"));
} else {
resolve(decoded);
}
},
);
});
}

/**
* Input parameters for SocketIOApiClient construction.
*/
Expand Down Expand Up @@ -96,7 +63,7 @@ export class SocketIOApiClient implements ISocketApiClient<SocketLedgerEvent> {
checkValidator: (
publicKey: string,
data: string,
) => Promise<JwtPayload> = verifyValidatorJwt;
) => Promise<JwtPayload> = verifyValidatorMessageJwt;

/**
* @param validatorID - (required) ID of validator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* Don't use jest timer mocks here, they do not work well with node http module.
* With timer mocks tests will either hang or report open timeout handle.
* Tests:
* - verifyValidatorJwt(),
* - SocketIOApiClient construction,
* - SocketIOApiClient.sendAsyncRequest(),
* - SocketIOApiClient.sendSyncRequest(),
Expand All @@ -17,7 +16,6 @@ const setupTimeout = 1000 * 60; // 1 minute timeout for setup

import "jest-extended";
import { cloneDeep } from "lodash";
import { sign, JwtPayload } from "jsonwebtoken";

// Unit Test logger setup
import {
Expand Down Expand Up @@ -48,7 +46,7 @@ const defaultConfigOptions = {

// Generate private / public keys for test purposes
import { generateKeyPairSync } from "crypto";
const { publicKey, privateKey } = generateKeyPairSync("ec", {
const { publicKey } = generateKeyPairSync("ec", {
namedCurve: "P-256",
});
const publicKeyString = publicKey.export({
Expand All @@ -64,7 +62,6 @@ jest.spyOn(fs, "readFileSync").mockReturnValue(publicKeyString);

import {
SocketIOApiClient,
verifyValidatorJwt,
SocketLedgerEvent,
} from "../../../main/typescript/socketio-api-client";

Expand All @@ -74,81 +71,6 @@ import {

jest.setTimeout(testTimeout);

//////////////////////////////////
// verifyValidatorJwt()
//////////////////////////////////

describe("verifyValidatorJwt tests", () => {
const message = {
message: "Hello",
from: "Someone",
};
let signedMessage = "";

beforeAll(() => {
log.debug("input message:", message);

// Encrypt the message (from validator)
signedMessage = sign(
message,
privateKey.export({ type: "sec1", format: "pem" }),
{
algorithm: "ES256",
expiresIn: "1 day",
},
);
expect(signedMessage).toBeTruthy();
log.debug("signedMessage:", signedMessage);
}, setupTimeout);

test("Decrypts the payload from the validator using it's public key", async () => {
// Verify (decrypt)
const decryptedMessage = await verifyValidatorJwt(
publicKeyString,
signedMessage,
);

// Assert decrypted message
log.debug("decryptedMessage:", decryptedMessage);
expect(decryptedMessage).toMatchObject(message);
const decryptedJwt = decryptedMessage as JwtPayload;
expect(decryptedJwt.iat).toBeNumber();
expect(decryptedJwt.exp).toBeNumber();
});

test("Rejects malicious message", () => {
// Reverse original message to produce wrong input
const maliciousMessage = signedMessage.split("").reverse().join("");
log.debug("maliciousMessage", maliciousMessage);

// Verify (decrypt)
return expect(
verifyValidatorJwt(publicKeyString, maliciousMessage),
).toReject();
});

test("Rejects expired message", (done) => {
// Encrypt the message (from validator) with short expiration time
signedMessage = sign(
message,
privateKey.export({ type: "sec1", format: "pem" }),
{
algorithm: "ES256",
expiresIn: "1",
},
);
expect(signedMessage).toBeTruthy();

setTimeout(async () => {
// Verify after short timeout
await expect(
verifyValidatorJwt(publicKeyString, signedMessage),
).toReject();
done();
}, 1000);
});
});

//////////////////////////////////
// SocketIOApiClient Constructor
//////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions packages/cactus-cmd-socketio-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@hyperledger/cactus-core-api": "1.0.0",
"@types/node": "14.17.32",
"body-parser": "1.19.2",
"config": "3.3.7",
"cookie-parser": "1.4.5",
"debug": "2.6.9",
"escape-html": "1.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export { Verifier } from "./verifier/Verifier";
export { LedgerEvent } from "./verifier/LedgerPlugin";
export { json2str } from "./verifier/DriverCommon";
export { signMessageJwt } from './verifier/validator-authentication';

// Routing Interface
export { RIFError } from "./routing-interface/RIFError";
Expand All @@ -25,3 +26,4 @@ export { LedgerOperation } from "./business-logic-plugin/LedgerOperation";

// Util
export { TransactionSigner } from "./util/TransactionSigner";
export { configRead } from "./util/config";
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
/*
* Copyright 2021 Hyperledger Cactus Contributors
* Copyright 2022 Hyperledger Cactus Contributors
* SPDX-License-Identifier: Apache-2.0
*
* NOTE: Be sure that NODE_CONFIG_DIR env variable points to the location
* of current module config files before loading this.
*
* config.js
*/

export const DEFAULT_NODE_CONFIG_DIR = "/etc/cactus/connector-fabric-socketio/";
if (!process.env["NODE_CONFIG_DIR"]) {
// Must be set before import config
process.env["NODE_CONFIG_DIR"] = DEFAULT_NODE_CONFIG_DIR;
}

import config from "config";

Expand All @@ -20,7 +18,7 @@ import config from "config";
* @param defaultValue : Value to return if key is not present in the config.
* @returns : Configuration value
*/
export function read<T>(key: string, defaultValue?: T): T {
export function configRead<T>(key: string, defaultValue?: T): T {
if (config.has(key)) {
return config.get(key);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2020-2022 Hyperledger Cactus Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import fs from "fs";
import jwt from "jsonwebtoken";
import { configRead } from "../util/config";
import { signValidatorMessageJwt } from "@hyperledger/cactus-common"

// Will keep the private key once it's succesfully read
let privateKey: string;

/**
* Validator-side function to sign message to be sent to the client.
* Will read the private key either as value in validator config `sslParam.keyValue`,
* or read from filesystem under path `sslParam.key`.
*
* @param payload - Message to sign
* @returns Signed message
*/
export function signMessageJwt(payload: object): string {
if (!privateKey) {
try {
privateKey = configRead<string>('sslParam.keyValue');
} catch {
privateKey = fs.readFileSync(configRead('sslParam.key'), "ascii");
}
}
const jwtAlgo = configRead<jwt.Algorithm>('sslParam.jwtAlgo', 'ES256');
return signValidatorMessageJwt(privateKey, payload, jwtAlgo);
}
Loading

0 comments on commit 200a7da

Please sign in to comment.