diff --git a/.cspell.json b/.cspell.json index a74cd44451..babc8ef66e 100644 --- a/.cspell.json +++ b/.cspell.json @@ -25,6 +25,7 @@ "DHTAPI", "DockerOde", "ealen", + "ecparams", "Errorf", "escc", "execa", @@ -49,8 +50,11 @@ "isready", "jboss", "JORDI", + "jsrsasign", "Keychain", "Keycloak", + "KEYUTIL", + "KJUR", "Knetic", "LEDGERBLOCKACK", "lmify", @@ -85,7 +89,9 @@ "protoc", "protos", "RUSTC", + "sbjpubkey", "Secp", + "shrn", "socketio", "SPDX", "Sprintf", diff --git a/packages/cactus-plugin-ledger-connector-fabric/README.md b/packages/cactus-plugin-ledger-connector-fabric/README.md index e5232f8a14..fe8e099c8f 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/README.md +++ b/packages/cactus-plugin-ledger-connector-fabric/README.md @@ -10,6 +10,7 @@ This plugin provides `Cactus` a way to interact with Fabric networks. Using this - [Getting Started](#getting-started) - [Architecture](#architecture) - [Usage](#usage) + - [Identity Providers](#identity-providers) - [Runing the tests](#running-the-tests) - [Built With](#built-with) - [Prometheus Exporter](#prometheus-exporter) @@ -43,6 +44,10 @@ The above diagram shows the sequence diagram of run-transaction-endpoint. User A ![run-transaction-endpoint transact() method](docs/architecture/images/run-transaction-endpoint-transact.png) The above diagram shows the sequence diagraom of transact() method of the PluginLedgerConnectorFabric class. The caller to this function, which in reference to the above sequence diagram is API server, sends RunTransactionRequest object as an argument to the transact() method. Based on the invocationType (FabricContractInvocationType.CALL, FabricCOntractInvocationType.SEND), corresponding responses are send back to the caller. +![run-transaction-endpoint-enroll](docs/architecture/images/run-transaction-endpoint-enroll.png) + +The above diagram shows the sequence diagraom of enroll() method of the PluginLedgerConnectorFabric class. The caller to this function, which in reference to the above sequence diagram is API server, sends Signer object along with EnrollmentRequest as an argument to the enroll() method. Based on the singerType (FabricSigningCredentialType.X509, FabricSigningCredentialType.VaultX509 .. more in TODO), corresponding identity is enrolled and stored inside keychain. + ## Usage To use this import public-api and create new **PluginLedgerConnectorFabric** and **ChainCodeCompiler**. @@ -66,8 +71,131 @@ For compile the chaincodes: const result = await compiler.compile(opts); ``` +To support signing of message with multiple identity types +```typescript +// vault server config for supporting vault identity provider +const vaultConfig:IVaultConfig = { + endpoint : "http://localhost:8200", + transitEngineMountPath: "/transit", +} +// provide list of identity signing to be supported +const supportedIdentity:FabricSigningCredentialType[] = [FabricSigningCredentialType.VaultX509,FabricSigningCredentialType.X509] +const pluginOptions:IPluginLedgerConnectorFabricOptions = { + // other options + vaultConfig : vaultConfig, + supportedIdentity:supportedIdentity + // .. other options +} +const connector: PluginLedgerConnectorFabric = new PluginLedgerConnectorFabric(pluginOptions); +``` + +To enroll an identity +```typescript +await connector.enroll( + { + keychainId: "keychain-identifier-for storing-certData", + keychainRef: "cert-data-identifier", + type: FabricSigningCredentialType.VaultX509, // FabricSigningCredentialType.X509 + + // require in case of vault + vaultTransitKey: { + token: "vault-token", + keyName: "vault-key-label", + }, + }, + { + enrollmentID: "client2", + enrollmentSecret: "pw", + mspId: "Org1MSP", + caId: "ca.org1.example.com", + }, + ); +``` +To Register an identity using register's key +```typescript +const secret = await connector.register( + { + keychainId: "keychain-id-that-store-certData-of-registrar", + keychainRef: "certData-label", + type: FabricSigningCredentialType.VaultX509, // FabricSigningCredentialType.X509 + + // require in case of vault + vaultTransitKey: { + token: testToken, + keyName: registrarKey, + }, + }, + { + enrollmentID: "client-enrollmentID", + enrollmentSecret: "pw", + affiliation: "org1.department1", + }, + "ca.org1.example.com", // caID + ); +``` + +To transact with fabric +```typescript +const resp = await connector.transact{ + signingCredential: { + keychainId: keychainId, + keychainRef: "client-certData-id", + type: FabricSigningCredentialType.VaultX509, // FabricSigningCredentialType.X509 + + // require in case of vault + vaultTransitKey: { + token: testToken, + keyName: registrarKey, + }, + }, + // .. other options +} +``` + +To Rotate the key +```typescript +await connector.rotateKey( + { + keychainId: keychainId, + keychainRef: "client-certData-id", + type: FabricSigningCredentialType.VaultX509, // FabricSigningCredentialType.X509 + + // require in case of vault + vaultTransitKey: { + token: testToken, + keyName: registrarKey, + }, + }, + { + enrollmentID: string; + enrollmentSecret: string; + caId: string; + } +) +``` > Extensive documentation and examples in the [readthedocs](https://readthedocs.org/projects/hyperledger-cactus/) (WIP) +## Identity Providers + +Identity providers allows client to manage their private more effectively and securely. Cactus Fabric Connector support multiple type of providers. Each provider differ based upon where the private are stored. On High level certificate credential are stored as + +```typescript +{ + type: FabricSigningCredentialType; + credentials: { + certificate: string; + // if identity type is IdentityProvidersType.X509 + privateKey?: string; + }; + mspId: string; +} +``` + +Currently Cactus Fabric Connector supports following Identity Providers + +- X509 : Simple and unsecured provider wherein `private` key is stored along with certificate in some `datastore`. Whenever connector require signature on fabric message, private key is brought from the `datastore` and message signed at the connector. +- Vault-X.509 : Secure provider wherein `private` key is stored with vault's transit transit engine and certificate in `certDatastore`. Rather then bringing the key to the connector, message digest are sent to the vault server which returns the `signature`. +- WS-X.509 (Future Work) : Secure provider wherein `private` key is stored with `client` and certificate in `certDatastore`. To get the fabric messages signed, message digest is sent to the client via `webSocket` connection opened by the client in the beginning. ## Running the tests diff --git a/packages/cactus-plugin-ledger-connector-fabric/docs/architecture/images/run-transaction-endpoint-enroll.png b/packages/cactus-plugin-ledger-connector-fabric/docs/architecture/images/run-transaction-endpoint-enroll.png new file mode 100644 index 0000000000..76310b39e2 Binary files /dev/null and b/packages/cactus-plugin-ledger-connector-fabric/docs/architecture/images/run-transaction-endpoint-enroll.png differ diff --git a/packages/cactus-plugin-ledger-connector-fabric/docs/architecture/run-transaction-endpoint-enroll.puml b/packages/cactus-plugin-ledger-connector-fabric/docs/architecture/run-transaction-endpoint-enroll.puml new file mode 100644 index 0000000000..934e73f7ed --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/docs/architecture/run-transaction-endpoint-enroll.puml @@ -0,0 +1,42 @@ +@startuml +title Hyperledger Cactus\nSequence Diagram\nRun Transaction Endpoint\enroll() method + +skinparam sequenceArrowThickness 2 +skinparam roundcorner 20 +skinparam maxmessagesize 300 +skinparam sequenceParticipant underline + +actor "Caller" as caller +participant "PluginLedgerConnectorFabric" as t << (C,#ADD1B2) class >> + +autoactivate on + +activate caller +caller -> t : enroll(signer: FabricSigningCredential,\nreq: FabricEnrollmentRequest) +t->t: {\n\tenrollmentID: string,\n\tenrollmentSecret: string,\n\tcaId: string,\n\tmspId: string\n} + +alt #LightBlue this.activatedSinger.includes(singer.type) === false + t-->caller: throw Error(`singer.type not activated`) +end + +t->t : ca = this.createCaClient(caId) +t->t : enrollmentRequest = {enrollmentID,enrollmentSecret} + +group #LightBlue if singer.type == FabricSigningCredentialType.DeafultX509 +else #LightYellow if singer.type == FabricSigningCredentialType.VaulttX509 + t->t : enrollmentRequest.csr = await (this.activatedSinger[VaulttX509] as VaultX509Provider)\n\t.getKey(singer.vaultTransitKey)\n\t.generateCSR(enrollmentID) +else #LightCoral default + t -> caller: throw Error('unknown SingerType') +end + +t->t: resp = await ca.enroll(enrollmentRequest) +t->t: certData = {singer.type,mspId,certificate} + +alt if resp.key !== undefined + t->t: resp.credentials.privateKey = resp.key.toBytes(); +end + +t->t : await certDatastore.put(singer.keychainref,certData) + +deactivate caller +@enduml \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-fabric/package.json b/packages/cactus-plugin-ledger-connector-fabric/package.json index d6d8685668..745f515f70 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/package.json +++ b/packages/cactus-plugin-ledger-connector-fabric/package.json @@ -78,16 +78,19 @@ "@hyperledger/cactus-core-api": "0.8.0", "axios": "0.21.1", "bl": "5.0.0", + "bn.js": "4.12.0", "express": "4.17.1", - "fabric-ca-client": "2.2.8", - "fabric-common": "2.2.8", - "fabric-network": "2.2.8", + "fabric-ca-client": "2.3.0-snapshot.49", + "fabric-common": "2.3.0-snapshot.49", + "fabric-network": "2.3.0-snapshot.49", "fabric-protos": "2.2.8", "form-data": "4.0.0", "http-status-codes": "2.1.4", + "jsrsasign": "10.4.0", "multer": "1.4.3", "ngo": "2.7.0", "node-ssh": "12.0.0", + "node-vault": "0.9.22", "openapi-types": "9.1.0", "prom-client": "13.2.0", "temp": "0.9.4", @@ -99,7 +102,9 @@ "@hyperledger/cactus-test-tooling": "0.8.0", "@types/express": "4.17.13", "@types/fs-extra": "9.0.12", + "@types/jsrsasign": "8.0.13", "@types/multer": "1.4.7", + "@types/node-vault": "0.9.13", "@types/temp": "0.9.1", "@types/uuid": "8.3.1", "fs-extra": "10.0.0" diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json index 793826836a..0244a5bed3 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json @@ -31,6 +31,40 @@ ], "components": { "schemas": { + "VaultTransitKey" : { + "type": "object", + "nullable": false, + "required": [ + "keyName", + "token" + ], + "properties": { + "keyName": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "nullable": false, + "description": "label of private key" + }, + "token": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "nullable": false, + "description": "token for accessing private key" + } + }, + "description": "vault key details for signing fabric message with private key stored with transit engine." + }, + "FabricSigningCredentialType" : { + "type": "string", + "enum": [ + "X.509", + "Vault-X.509" + ], + "nullable": false, + "description": "different type of identity provider for singing fabric messages supported by this package" + }, "FabricSigningCredential": { "type": "object", "required": [ @@ -49,6 +83,14 @@ "minLength": 1, "maxLength": 100, "nullable": false + }, + "type" : { + "$ref" : "#/components/schemas/FabricSigningCredentialType", + "description" : "singing identity type to be used for signing fabric message , by by default default is supported" + }, + "vaultTransitKey" : { + "$ref" : "#/components/schemas/VaultTransitKey", + "properties" : "vault key details , if Vault-X.509 identity provider to be used for singing fabric messages" } } }, diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts index 760ac4c65c..fb4ba76041 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/common/create-gateway.ts @@ -1,6 +1,7 @@ import { DefaultEventHandlerOptions } from "fabric-network"; -import { DefaultEventHandlerStrategies, Wallets } from "fabric-network"; +import { DefaultEventHandlerStrategies } from "fabric-network"; import { Gateway } from "fabric-network"; +import { ICryptoKey } from "fabric-common"; import { GatewayOptions as FabricGatewayOptions } from "fabric-network"; import { Checks, LoggerProvider } from "@hyperledger/cactus-common"; import { LogLevelDesc } from "@hyperledger/cactus-common"; @@ -8,7 +9,18 @@ import { PluginRegistry } from "@hyperledger/cactus-core"; import { ConnectionProfile } from "../generated/openapi/typescript-axios/index"; import { GatewayDiscoveryOptions } from "../generated/openapi/typescript-axios/index"; import { GatewayEventHandlerOptions } from "../generated/openapi/typescript-axios/index"; -import { GatewayOptions } from "../generated/openapi/typescript-axios/index"; +import { + GatewayOptions, + FabricSigningCredentialType, +} from "../generated/openapi/typescript-axios/index"; +import { + CertDatastore, + IIdentityData, +} from "../identity/internal/cert-datastore"; +import { + IIdentity, + SecureIdentityProviders, +} from "../identity/identity-provider"; export interface ICreateGatewayContext { readonly logLevel?: LogLevelDesc; @@ -17,6 +29,8 @@ export interface ICreateGatewayContext { readonly defaultDiscoveryOptions: GatewayDiscoveryOptions; readonly defaultEventHandlerOptions: GatewayEventHandlerOptions; readonly gatewayOptions: GatewayOptions; + readonly certStore: CertDatastore; + readonly secureIdentity: SecureIdentityProviders; } export const E_CREATE_GATEWAY_WALLET = @@ -37,26 +51,61 @@ export async function createGateway( const { defaultConnectionProfile } = ctx; const cp = ctx.gatewayOptions.connectionProfile || defaultConnectionProfile; - const wallet = await Wallets.newInMemoryWallet(); - - let identity; + let certData: IIdentityData; if (ctx.gatewayOptions.wallet.json) { log.debug("Parsing wallet from JSON representation..."); - identity = JSON.parse(ctx.gatewayOptions.wallet.json); + certData = JSON.parse(ctx.gatewayOptions.wallet.json); + certData.type = certData.type || FabricSigningCredentialType.X509; } else if (ctx.gatewayOptions.wallet.keychain) { log.debug("Fetching wallet from JSON keychain..."); - const keychain = ctx.pluginRegistry.findOneByKeychainId( + certData = await ctx.certStore.get( ctx.gatewayOptions.wallet.keychain.keychainId, - ); - identity = await keychain.get( ctx.gatewayOptions.wallet.keychain.keychainRef, ); + ctx.gatewayOptions.wallet.keychain.type = + ctx.gatewayOptions.wallet.keychain.type || + FabricSigningCredentialType.X509; + if (certData.type !== ctx.gatewayOptions.wallet.keychain.type) { + throw new Error( + `identity type mismatch, sorted of type = ${certData.type} but provided = ${ctx.gatewayOptions.wallet.keychain.type}`, + ); + } } else { throw new Error(E_CREATE_GATEWAY_WALLET); } - await wallet.put(ctx.gatewayOptions.identity, identity); - log.debug(`Imported identity ${ctx.gatewayOptions.identity} to wallet OK`); + let key: ICryptoKey; + switch (certData.type) { + case FabricSigningCredentialType.VaultX509: + if ( + !ctx.gatewayOptions.wallet.keychain || + !ctx.gatewayOptions.wallet.keychain.vaultTransitKey + ) { + throw new Error( + `require ctx.gatewayOptions.wallet.keychain.vaultTransitKey`, + ); + } + key = ctx.secureIdentity.getVaultKey({ + token: ctx.gatewayOptions.wallet.keychain.vaultTransitKey.token, + keyName: ctx.gatewayOptions.wallet.keychain.vaultTransitKey.keyName, + }); + break; + case FabricSigningCredentialType.X509: + key = ctx.secureIdentity.getDefaultKey({ + private: certData.credentials.privateKey as string, + }); + break; + default: + throw new Error(`UNRECOGNIZED_IDENTITY_TYPE type = ${certData.type}`); + } + const identity: IIdentity = { + type: certData.type, + mspId: certData.mspId, + credentials: { + certificate: certData.credentials.certificate, + key: key, + }, + }; const eventHandlerOptions: DefaultEventHandlerOptions = { commitTimeout: ctx.gatewayOptions.eventHandlerOptions?.commitTimeout || 300, @@ -77,8 +126,8 @@ export async function createGateway( const gatewayOptions: FabricGatewayOptions = { discovery: ctx.gatewayOptions.discovery || ctx.defaultDiscoveryOptions, eventHandlerOptions, - identity: ctx.gatewayOptions.identity, - wallet, + identity: identity, + identityProvider: ctx.secureIdentity, }; log.debug("Instantiating and connecting gateway..."); diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts index a29c68fcf2..74678db162 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -535,7 +535,29 @@ export interface FabricSigningCredential { * @memberof FabricSigningCredential */ keychainRef: string; + /** + * + * @type {FabricSigningCredentialType} + * @memberof FabricSigningCredential + */ + type?: FabricSigningCredentialType; + /** + * + * @type {VaultTransitKey} + * @memberof FabricSigningCredential + */ + vaultTransitKey?: VaultTransitKey; +} +/** + * different type of identity provider for singing fabric messages supported by this package + * @export + * @enum {string} + */ +export enum FabricSigningCredentialType { + X509 = 'X.509', + VaultX509 = 'Vault-X.509' } + /** * Represents a file-system file that has a name and a body which holds the file contents as a Base64 encoded string * @export @@ -791,6 +813,25 @@ export interface SSHExecCommandResponse { */ signal: string | null; } +/** + * vault key details for signing fabric message with private key stored with transit engine. + * @export + * @interface VaultTransitKey + */ +export interface VaultTransitKey { + /** + * label of private key + * @type {string} + * @memberof VaultTransitKey + */ + keyName: string; + /** + * token for accessing private key + * @type {string} + * @memberof VaultTransitKey + */ + token: string; +} /** * DefaultApi - axios parameter creator diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/identity-provider.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/identity-provider.ts new file mode 100644 index 0000000000..00dcb2914e --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/identity-provider.ts @@ -0,0 +1,134 @@ +import { IdentityProvider, IdentityData, Identity } from "fabric-network"; +import { FabricSigningCredentialType } from "../generated/openapi/typescript-axios/api"; +import { Checks } from "@hyperledger/cactus-common"; +import { ICryptoSuite, User, Utils, ICryptoKey } from "fabric-common"; +import { + LogLevelDesc, + Logger, + LoggerProvider, +} from "@hyperledger/cactus-common"; +import { Key } from "./internal/key"; +import { InternalCryptoSuite } from "./internal/crypto-suite"; +import { VaultTransitClient } from "./vault-client"; + +export interface IVaultConfig { + endpoint: string; + transitEngineMountPath: string; +} + +export interface ISecureIdentityProvidersOptions { + activatedProviders: FabricSigningCredentialType[]; + logLevel: LogLevelDesc; + + // vault server config + vaultConfig?: IVaultConfig; +} + +export interface IIdentity extends Identity { + type: FabricSigningCredentialType; + credentials: { + certificate: string; + key: ICryptoKey; + }; +} + +export interface VaultKey { + keyName: string; + token: string; +} + +export interface DefaultKey { + // pem encoded private key + private: string; +} + +// SecureIdentityProviders : a entry point class to various secure identity provider +// some of the function are just to support the interface provided by the fabric-sdk-node +export class SecureIdentityProviders implements IdentityProvider { + private readonly log: Logger; + public readonly className = "SecureIdentityProviders"; + private readonly defaultSuite: ICryptoSuite; + constructor(private readonly opts: ISecureIdentityProvidersOptions) { + const fnTag = `${this.className}#constructor`; + this.log = LoggerProvider.getOrCreate({ + level: opts.logLevel || "INFO", + label: this.className, + }); + if ( + opts.activatedProviders.includes(FabricSigningCredentialType.VaultX509) + ) { + if (!opts.vaultConfig) { + throw new Error(`${fnTag} require options.vaultConfig`); + } + Checks.nonBlankString( + opts.vaultConfig.endpoint, + `${fnTag} options.vaultConfig.endpoint`, + ); + Checks.nonBlankString( + opts.vaultConfig.transitEngineMountPath, + `${fnTag} options.vaultConfig.transitEngineMountPath`, + ); + this.log.debug(`${fnTag} Vault-X.509 identity provider activated`); + } + this.defaultSuite = Utils.newCryptoSuite(); + } + + async getUserContext(identity: IIdentity, name: string): Promise { + const fnTag = `${this.className}#getUserContext`; + Checks.truthy(identity, `${fnTag} identity`); + if (!this.opts.activatedProviders.includes(identity.type)) { + throw new Error( + `${fnTag} identity type = ${identity.type} not activated`, + ); + } + Checks.truthy(identity.credentials, `${fnTag} identity.credentials`); + Checks.nonBlankString( + identity.credentials.certificate, + `${fnTag} identity.credentials.certificate`, + ); + Checks.truthy( + identity.credentials.key, + `${fnTag} identity.credentials.key`, + ); + const user = new User(name); + if (identity.type === FabricSigningCredentialType.X509) { + user.setCryptoSuite(this.defaultSuite); + } else { + user.setCryptoSuite(new InternalCryptoSuite()); + } + await user.setEnrollment( + identity.credentials.key, + identity.credentials.certificate, + identity.mspId, + ); + return user; + } + + getVaultKey(key: VaultKey): Key { + return new Key( + key.keyName, + new VaultTransitClient({ + endpoint: this.opts.vaultConfig?.endpoint as string, + mountPath: this.opts.vaultConfig?.transitEngineMountPath as string, + token: key.token, + logLevel: this.opts.logLevel, + }), + ); + } + + getDefaultKey(key: DefaultKey): ICryptoKey { + return this.defaultSuite.createKeyFromRaw(key.private); + } + + // not required things + readonly type = ""; + getCryptoSuite(): ICryptoSuite { + throw new Error("SecureIdentityProviders::getCryptoSuite not required!!"); + } + fromJson(): Identity { + throw new Error("SecureIdentityProviders::fromJson not required!!"); + } + toJson(): IdentityData { + throw new Error("SecureIdentityProviders::toJso : not required!!"); + } +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/cert-datastore.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/cert-datastore.ts new file mode 100644 index 0000000000..b09d9cfb52 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/cert-datastore.ts @@ -0,0 +1,37 @@ +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { FabricSigningCredentialType } from "../../generated/openapi/typescript-axios/api"; + +// IIdentityData : data that will be stored with cert datastore +// with key as client's commonName (from X509 certificate) and value as following field +export interface IIdentityData { + type: FabricSigningCredentialType; + credentials: { + certificate: string; + // if identity type is IdentityProvidersType.Default + privateKey?: string; + }; + mspId: string; +} + +// sweet wrapper for managing client's certificate +// stored within multiple keychain registered to +// plugin registry +export class CertDatastore { + constructor(private readonly pluginRegistry: PluginRegistry) {} + async get(keychainId: string, keychainRef: string): Promise { + const keychain = this.pluginRegistry.findOneByKeychainId(keychainId); + return JSON.parse(await keychain.get(keychainRef)); + } + + async put( + keychainId: string, + keychainRef: string, + iData: IIdentityData, + ): Promise { + const keychain = this.pluginRegistry.findOneByKeychainId(keychainId); + await keychain.set(keychainRef, JSON.stringify(iData)); + } + + // TODO has + // TODO delete +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/client.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/client.ts new file mode 100644 index 0000000000..27bd3f7e1f --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/client.ts @@ -0,0 +1,31 @@ +import { KJUR } from "jsrsasign"; +import { ECCurveType } from "./crypto-util"; + +export interface ISignatureResponse { + sig: Buffer; + crv: ECCurveType; +} + +// class that all the identity provider should implement +export abstract class InternalIdentityClient { + /** + * @description send message digest to the client for it to be signed by the private key stored with the client + * @param keyName , label of the key + * @param digest : messages digest which need to signed (NOTE : digest will already be hashed) + * @returns asn1 encoded signature + */ + abstract sign(keyName: string, digest: Buffer): Promise; + + /** + * @description get the the public key from the client + * @param keyName for which public key should be returned + * @returns ECDSA key only p256 and p384 curve are supported + */ + abstract getPub(keyName: string): Promise; + + /** + * @description will rotate a given key + * @param keyName label of key that need to be rotated + */ + abstract rotateKey(keyName: string): Promise; +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/crypto-suite.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/crypto-suite.ts new file mode 100644 index 0000000000..40f7e4159c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/crypto-suite.ts @@ -0,0 +1,45 @@ +import { ICryptoSuite, ICryptoKey } from "fabric-common"; +import { createHash } from "crypto"; +import { Key } from "./key"; +import { Utils } from "fabric-common"; + +// InternalCryptoSuite : a class which will be implemented by identity provider +// some of the function are just to support the interface provided by the fabric-sdk-node +export class InternalCryptoSuite implements ICryptoSuite { + createKeyFromRaw(pem: string): ICryptoKey { + return Utils.newCryptoSuite().createKeyFromRaw(pem); + } + decrypt(): Buffer { + throw new Error("InternalCryptoSuite::decrypt : not required!!"); + } + deriveKey(): ICryptoKey { + throw new Error("InternalCryptoSuite::deriveKey : not required!!"); + } + encrypt(): Buffer { + throw new Error("InternalCryptoSuite::encrypt : not required!!"); + } + getKey(): Promise { + throw new Error("InternalCryptoSuite::getKey : not required!!"); + } + getKeySize(): number { + throw new Error("InternalCryptoSuite::getKeySize : not required!!"); + } + generateKey(): Promise { + throw new Error("InternalCryptoSuite::generateKey : not required!!"); + } + hash(msg: string): string { + return createHash("sha256").update(msg).digest("hex"); + } + importKey(): ICryptoKey | Promise { + throw new Error("InternalCryptoSuite::importKey : not required!!"); + } + setCryptoKeyStore(): void { + throw new Error("InternalCryptoSuite::setCryptoKeyStore : not required!!"); + } + async sign(key: Key, digest: Buffer): Promise { + return await key.sign(digest); + } + verify(): boolean { + throw new Error("InternalCryptoSuite::verify : not required!!"); + } +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/crypto-util.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/crypto-util.ts new file mode 100644 index 0000000000..0942be1478 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/crypto-util.ts @@ -0,0 +1,87 @@ +import { createHash } from "crypto"; +import { KJUR } from "jsrsasign"; +import BN from "bn.js"; + +const csrNamespace = KJUR.asn1.csr as any; + +// hex encoded prime order of NIST P-256 curve +// more information at https://safecurves.cr.yp.to/base.html +const ecdsa = new KJUR.crypto.ECDSA({ curve: "NIST P-256" }); +const p256N = new BN(((ecdsa as any).ecparams.n as BigInteger).toString(), 10); +// hex encoded prime order of NIST P-384 curve +ecdsa.setNamedCurve("NIST P-384"); +const p384N = new BN(((ecdsa as any).ecparams.n as BigInteger).toString(), 10); + +export enum ECCurveType { + P256 = "p256", + P384 = "p384", +} + +// class with all static function +// provide crypto util to identity providers +export class CryptoUtil { + public static readonly className = "CryptoUtil"; + // convert asn1 encoded signature to a fabric understandable signature format + // more info at https://github.com/hyperledger/fabric-sdk-node/blob/b562ae4d7b8c690cd008c98ff24dfd3fb78ade81/fabric-common/lib/impl/bccsp_pkcs11.js#L39 + static encodeASN1Sig(sig: Buffer, curve: ECCurveType): Buffer { + const fnTag = `${CryptoUtil.className}#encodeASN1Sig`; + const pSig = (KJUR.crypto.ECDSA as any).parseSigHexInHexRS( + sig.toString("hex"), + ) as { r: string; s: string }; + const r = new BN(pSig.r, "hex"); + let s = new BN(pSig.s, "hex"); + let crv: BN; + switch (curve) { + case ECCurveType.P256: + crv = p256N; + break; + case ECCurveType.P384: + crv = p384N; + break; + default: + throw new Error(`${fnTag} invalid ec curve type`); + } + const halfOrder = crv.shrn(1); + if (s.cmp(halfOrder) === 1) { + const bigNum = crv as BN; + s = bigNum.sub(s); + } + const encodedSig = KJUR.crypto.ECDSA.hexRSSigToASN1Sig( + r.toString("hex"), + s.toString("hex"), + ); + return Buffer.from(encodedSig, "hex"); + } + + // create a csr information using public key and commonName + // return s csr object + static createCSR( + pub: KJUR.crypto.ECDSA, + commonName: string, + ): KJUR.asn1.csr.CertificationRequest { + return new csrNamespace.CertificationRequest({ + subject: { str: "/CN=" + commonName }, + sbjpubkey: pub, + sigalg: "SHA256withECDSA", + }); + } + + // return csr digest to e signed by a private key + // signature should be a asn1 der encoded + static getCSRDigest(csr: KJUR.asn1.csr.CertificationRequest): Buffer { + const csrInfo = new csrNamespace.CertificationRequestInfo( + (csr as any).params, + ); + return createHash("sha256").update(csrInfo.getEncodedHex(), "hex").digest(); + } + + // generate a pem encoded csr + static getPemCSR( + _csr: KJUR.asn1.csr.CertificationRequest, + signature: Buffer, + ): string { + const csr = _csr as any; + csr.params.sighex = signature.toString("hex"); + return csr.getPEM(); + } +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/key.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/key.ts new file mode 100644 index 0000000000..c581c022e7 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/internal/key.ts @@ -0,0 +1,54 @@ +import { ICryptoKey } from "fabric-common"; +import { InternalIdentityClient } from "./client"; +import { CryptoUtil } from "./crypto-util"; + +// internal class used by cryptoSuite, this is just to support interface provided by +// fabric-sdk-node +export class Key implements ICryptoKey { + constructor( + private readonly keyName: string, + private readonly client: InternalIdentityClient, + ) {} + async sign(digest: Buffer): Promise { + const { sig, crv } = await this.client.sign(this.keyName, digest); + return CryptoUtil.encodeASN1Sig(sig, crv); + } + + /** + * @description generate a csr + * @param commonName + * @returns pem encoded csr string + */ + async generateCSR(commonName: string): Promise { + const pub = await this.client.getPub(this.keyName); + const csr = CryptoUtil.createCSR(pub, commonName); + const digest = CryptoUtil.getCSRDigest(csr); + const { sig } = await this.client.sign(this.keyName, digest); + return CryptoUtil.getPemCSR(csr, sig); + } + + /** + * @description will rotate the key + */ + async rotate(): Promise { + await this.client.rotateKey(this.keyName); + } + getSKI(): string { + throw new Error("Key::getSKI not-required"); + } + getHandle(): string { + throw new Error("Key::getHandle not-required"); + } + isSymmetric(): boolean { + throw new Error("Key::isSymmetric not-required"); + } + isPrivate(): boolean { + throw new Error("Key::isPrivate not-required"); + } + getPublicKey(): ICryptoKey { + throw new Error("Key::getPublicKey not-required"); + } + toBytes(): string { + throw new Error("Key::toBytes not-required"); + } +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/vault-client.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/vault-client.ts new file mode 100644 index 0000000000..a720995b16 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/identity/vault-client.ts @@ -0,0 +1,117 @@ +import { + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import Vault, { client } from "node-vault"; +import { KJUR, KEYUTIL } from "jsrsasign"; +import { InternalIdentityClient, ISignatureResponse } from "./internal/client"; +import { ECCurveType } from "./internal/crypto-util"; + +export interface IVaultTransitClientOptions { + // full url of vault server + // eg : http://localhost:8200 + endpoint: string; + + // mountPath of transit secret engine + // eg : /transit + mountPath: string; + + // token of the client + token: string; + + logLevel?: LogLevelDesc; +} + +export class VaultTransitClient implements InternalIdentityClient { + public readonly className = "VaultTransitClient"; + private readonly log: Logger; + private readonly backend: client; + constructor(opts: IVaultTransitClientOptions) { + this.log = LoggerProvider.getOrCreate({ + label: "VaultTransitClient", + level: opts.logLevel || "INFO", + }); + this.backend = Vault({ + endpoint: opts.endpoint, + apiVersion: "v1", + token: opts.token, + pathPrefix: opts.mountPath, + }); + } + + /** + * @description send message digest to be signed by private key stored on vault + * @param digest : messages digest which need to signed + * @param preHashed : is digest already hashed + * @returns asn1 encoded signature + */ + async sign(keyName: string, digest: Buffer): Promise { + const fnTag = `${this.className}#sign`; + this.log.debug( + `${fnTag} sign with key = ${keyName} , digestSize = ${digest.length}`, + ); + const pub = await this.getPub(keyName); + let crv = ECCurveType.P256; + if ((pub as any).curveName === "secp384r1") { + crv = ECCurveType.P384; + } + const resp = await this.backend.write("sign/" + keyName, { + input: digest.toString("base64"), + prehashed: true, + marshaling_algorithm: "asn1", + }); + this.log.debug(`${fnTag} got response from vault : %o`, resp.data); + if (resp?.data?.signature) { + const base64Sig = (resp.data.signature as string).split(":")[2]; + return { + sig: Buffer.from(base64Sig, "base64"), + crv: crv, + }; + } + throw new Error(`invalid response from vault ${JSON.stringify(resp)}`); + } + + /** + * @description return public key of latest version + * @param keyName for which public key should be returned + * @returns pem encoded public key + */ + async getPub(keyName: string): Promise { + const fnTag = `${this.className}#getPub`; + this.log.debug(`${fnTag} keyName = ${keyName}`); + try { + const resp = await this.backend.read("keys/" + keyName); + this.log.debug(`${fnTag} Response from Vault: %o`, JSON.stringify(resp)); + if (resp?.data?.latest_version && resp?.data?.keys) { + if (!["ecdsa-p256", "ecdsa-p384"].includes(resp.data.type)) { + throw new Error(`${fnTag} key = ${keyName} has invalid key type`); + } + // resp.data.keys has array of all the version of the key + // latest version is used for signing + return KEYUTIL.getKey( + resp.data.keys[resp.data.latest_version].public_key, + ) as KJUR.crypto.ECDSA; + } + throw new Error( + `${fnTag} invalid response from vault ${JSON.stringify(resp)}`, + ); + } catch (error) { + if ((error as any).response?.statusCode === 404) { + throw new Error(`${fnTag} keyName = ${keyName} not found`); + } + throw error; + } + } + + /** + * @description will rotate a given key + * @param keyName label of key that need to be rotated + */ + async rotateKey(keyName: string): Promise { + const fnTag = `${this.className}#rotateKey`; + this.log.debug(`${fnTag} rotate the kew ${keyName}`); + await this.backend.write("keys/" + keyName + "/rotate", {}); + this.log.debug(`${fnTag} key = ${keyName} successfully rotated`); + } +} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts index e02c205ca5..29b0a69d79 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts @@ -72,6 +72,7 @@ import { ChainCodeLifeCycleCommandResponses, FabricSigningCredential, DefaultEventHandlerStrategy, + FabricSigningCredentialType, } from "./generated/openapi/typescript-axios/index"; import { @@ -87,10 +88,21 @@ import { IDeployContractEndpointV1Options, } from "./deploy-contract/deploy-contract-endpoint-v1"; import { sourceLangToRuntimeLang } from "./peer/source-lang-to-runtime-lang"; -import FabricCAServices from "fabric-ca-client"; +import FabricCAServices, { + IEnrollmentRequest, + IRegisterRequest, +} from "fabric-ca-client"; import { createGateway } from "./common/create-gateway"; -import { Endorser } from "fabric-common"; - +import { Endorser, ICryptoKey } from "fabric-common"; +import { + IVaultConfig, + SecureIdentityProviders, + IIdentity, +} from "./identity/identity-provider"; +import { + CertDatastore, + IIdentityData, +} from "./identity/internal/cert-datastore"; /** * Constant value holding the default $GOPATH in the Fabric CLI container as * observed on fabric deployments that are produced by the official examples @@ -118,6 +130,8 @@ export interface IPluginLedgerConnectorFabricOptions prometheusExporter?: PrometheusExporter; discoveryOptions?: GatewayDiscoveryOptions; eventHandlerOptions?: GatewayEventHandlerOptions; + supportedIdentity?: FabricSigningCredentialType[]; + vaultConfig?: IVaultConfig; } export class PluginLedgerConnectorFabric @@ -139,6 +153,8 @@ export class PluginLedgerConnectorFabric private readonly cliContainerGoPath: string; public prometheusExporter: PrometheusExporter; private endpoints: IWebServiceEndpoint[] | undefined; + private readonly secureIdentity: SecureIdentityProviders; + private readonly certStore: CertDatastore; public get className(): string { return PluginLedgerConnectorFabric.CLASS_NAME; @@ -175,6 +191,15 @@ export class PluginLedgerConnectorFabric this.log = LoggerProvider.getOrCreate({ level, label }); this.instanceId = opts.instanceId; this.prometheusExporter.startMetricsCollection(); + // default is supported if supportedIdentity is empty + this.secureIdentity = new SecureIdentityProviders({ + activatedProviders: opts.supportedIdentity || [ + FabricSigningCredentialType.X509, + ], + logLevel: opts.logLevel || "INFO", + vaultConfig: opts.vaultConfig, + }); + this.certStore = new CertDatastore(opts.pluginRegistry); } public async shutdown(): Promise { @@ -823,6 +848,8 @@ export class PluginLedgerConnectorFabric strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, }, gatewayOptions: req.gatewayOptions, + secureIdentity: this.secureIdentity, + certStore: this.certStore, }); } else { return this.createGatewayLegacy(req.signingCredential); @@ -834,27 +861,44 @@ export class PluginLedgerConnectorFabric ): Promise { const { connectionProfile, eventHandlerOptions: eho } = this.opts; - const wallet = await Wallets.newInMemoryWallet(); + const iType = signingCredential.type || FabricSigningCredentialType.X509; - const keychain = this.opts.pluginRegistry.findOneByKeychainId( - signingCredential.keychainId, - ); - this.log.debug( - "transact() obtained keychain by ID=%o OK", + const certData = await this.certStore.get( signingCredential.keychainId, - ); - - const fabricX509IdentityJson = await keychain.get( signingCredential.keychainRef, ); - this.log.debug( - "transact() obtained keychain entry Key=%o OK", - signingCredential.keychainRef, - ); - const identity = JSON.parse(fabricX509IdentityJson); - - await wallet.put(signingCredential.keychainRef, identity); - this.log.debug("transact() imported identity to in-memory wallet OK"); + if (iType !== certData.type) { + throw new Error( + `identity type mismatch, sorted of type = ${certData.type} but provided = ${iType}`, + ); + } + let key: ICryptoKey; + switch (iType) { + case FabricSigningCredentialType.VaultX509: + if (!signingCredential.vaultTransitKey) { + throw new Error(`require signingCredential.vaultTransitKey`); + } + key = this.secureIdentity.getVaultKey({ + token: signingCredential.vaultTransitKey.token, + keyName: signingCredential.vaultTransitKey.keyName, + }); + break; + case FabricSigningCredentialType.X509: + key = this.secureIdentity.getDefaultKey({ + private: certData.credentials.privateKey as string, + }); + break; + default: + throw new Error(`UNRECOGNIZED_IDENTITY_TYPE type = ${iType}`); + } + const identity: IIdentity = { + type: iType, + mspId: certData.mspId, + credentials: { + certificate: certData.credentials.certificate, + key: key, + }, + }; const eventHandlerOptions: DefaultEventHandlerOptions = { commitTimeout: this.opts.eventHandlerOptions?.commitTimeout || 300, @@ -868,8 +912,8 @@ export class PluginLedgerConnectorFabric const gatewayOptions: GatewayOptions = { discovery: this.opts.discoveryOptions, eventHandlerOptions, - identity: signingCredential.keychainRef, - wallet, + identity: identity, + identityProvider: this.secureIdentity, }; this.log.debug(`discovery=%o`, gatewayOptions.discovery); @@ -904,6 +948,7 @@ export class PluginLedgerConnectorFabric try { const gateway = await this.createGateway(req); + // const gateway = await this.createGatewayLegacy(req.signingCredential); const network = await gateway.getNetwork(channelName); // const channel = network.getChannel(); // const endorsers = channel.getEndorsers(); @@ -997,6 +1042,7 @@ export class PluginLedgerConnectorFabric functionOutput: outUtf8, success, }; + gateway.disconnect(); this.log.debug(`transact() response: %o`, res); this.prometheusExporter.addCurrentTransaction(); @@ -1067,4 +1113,175 @@ export class PluginLedgerConnectorFabric throw new Error(`${fnTag} Exception: ${ex?.message}`); } } + /** + * @description enroll a client and store the enrolled certificate inside keychain + * @param identity details about client's key + * @param request , enroll request for fabric-ca-server + */ + public async enroll( + identity: FabricSigningCredential, + request: { + enrollmentID: string; + enrollmentSecret: string; + caId: string; + mspId: string; + }, + ): Promise { + const fnTag = `${this.className}#enroll`; + const iType = identity.type || FabricSigningCredentialType.X509; + this.log.debug( + `${fnTag} enroll identity of type = ${iType} with ca = ${request.caId}`, + ); + Checks.nonBlankString(identity.keychainId, `${fnTag} identity.keychainId`); + Checks.nonBlankString( + identity.keychainRef, + `${fnTag} identity.keychainRef`, + ); + Checks.nonBlankString(request.mspId, `${fnTag} request.mspId`); + const ca = await this.createCaClient(request.caId); + const enrollmentRequest: IEnrollmentRequest = { + enrollmentID: request.enrollmentID, + enrollmentSecret: request.enrollmentSecret, + }; + switch (iType) { + case FabricSigningCredentialType.VaultX509: + if (!identity.vaultTransitKey) { + throw new Error(`${fnTag} require identity.vaultTransitKey`); + } + const key = this.secureIdentity.getVaultKey({ + token: identity.vaultTransitKey.token, + keyName: identity.vaultTransitKey.keyName, + }); + enrollmentRequest.csr = await key.generateCSR(request.enrollmentID); + break; + } + const resp = await ca.enroll(enrollmentRequest); + const certData: IIdentityData = { + type: iType, + mspId: request.mspId, + credentials: { + certificate: resp.certificate, + }, + }; + if (resp.key) { + certData.credentials.privateKey = resp.key.toBytes(); + } + await this.certStore.put( + identity.keychainId, + identity.keychainRef, + certData, + ); + } + + public async register( + registrar: FabricSigningCredential, + request: IRegisterRequest, + caId: string, + ): Promise { + const fnTag = `${this.className}#register`; + const iType = registrar.type || FabricSigningCredentialType.X509; + this.log.debug( + `${fnTag} register client using registrar identity of type = ${iType}`, + ); + Checks.nonBlankString( + registrar.keychainId, + `${fnTag} registrar.keychainId`, + ); + Checks.nonBlankString( + registrar.keychainRef, + `${fnTag} registrar.keychainRef`, + ); + const certData = await this.certStore.get( + registrar.keychainId, + registrar.keychainRef, + ); + if (certData.type != iType) { + throw new Error( + `${fnTag} identity type mismatch, stored ${certData.type} but provided ${iType}`, + ); + } + let key: ICryptoKey; + switch (iType) { + case FabricSigningCredentialType.X509: + key = this.secureIdentity.getDefaultKey({ + private: certData.credentials.privateKey as string, + }); + break; + case FabricSigningCredentialType.VaultX509: + if (!registrar.vaultTransitKey) { + throw new Error(`${fnTag} require registrar.vaultTransitKey`); + } + key = this.secureIdentity.getVaultKey({ + token: registrar.vaultTransitKey.token, + keyName: registrar.vaultTransitKey.keyName, + }); + break; + default: + throw new Error(`${fnTag} UNRECOGNIZED_IDENTITY_TYPE type = ${iType}`); + } + const user = await this.secureIdentity.getUserContext( + { + type: iType, + credentials: { + certificate: certData.credentials.certificate, + key: key, + }, + mspId: certData.mspId, + }, + "registrar", + ); + + const ca = await this.createCaClient(caId); + return await ca.register(request, user); + } + + /** + * @description re-enroll a client with new private key + * @param identity + */ + public async rotateKey( + identity: FabricSigningCredential, + request: { + enrollmentID: string; + enrollmentSecret: string; + caId: string; + }, + ): Promise { + const fnTag = `${this.className}#rotateKey`; + const iType = identity.type || FabricSigningCredentialType.X509; + this.log.debug( + `${fnTag} identity of type = ${iType} with ca = ${request.caId}`, + ); + this.log.debug( + `${fnTag} enroll identity of type = ${iType} with ca = ${request.caId}`, + ); + Checks.nonBlankString(identity.keychainId, `${fnTag} identity.keychainId`); + Checks.nonBlankString( + identity.keychainRef, + `${fnTag} identity.keychainRef`, + ); + const certData = await this.certStore.get( + identity.keychainId, + identity.keychainRef, + ); + switch (iType) { + case FabricSigningCredentialType.VaultX509: + if (!identity.vaultTransitKey) { + throw new Error(`${fnTag} require identity.vaultTransitKey)`); + } + const key = this.secureIdentity.getVaultKey({ + keyName: identity.vaultTransitKey.keyName, + token: identity.vaultTransitKey.token, + }); + await key.rotate(); + break; + } + identity.type = iType; + await this.enroll(identity, { + enrollmentID: request.enrollmentID, + enrollmentSecret: request.enrollmentSecret, + caId: request.caId, + mspId: certData.mspId, + }); + } } diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts index 18f8810f52..e15a92a081 100755 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/public-api.ts @@ -22,3 +22,6 @@ export { ICompilationOptions, ICompilationResult, } from "./chain-code-compiler"; + +export { IVaultConfig } from "./identity/identity-provider"; +export { IIdentityData } from "./identity/internal/cert-datastore"; diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-with-identities.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-with-identities.test.ts new file mode 100644 index 0000000000..d6d894873a --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-with-identities.test.ts @@ -0,0 +1,371 @@ +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import test, { Test } from "tape-promise/tape"; +import { IPluginLedgerConnectorFabricOptions } from "../../../../main/typescript/plugin-ledger-connector-fabric"; +import { v4 as uuidv4 } from "uuid"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + DefaultEventHandlerStrategy, + FabricSigningCredentialType, + IVaultConfig, + PluginLedgerConnectorFabric, + IIdentityData, + FabricContractInvocationType, +} from "../../../../main/typescript/public-api"; +import { DiscoveryOptions } from "fabric-network"; + +const logLevel: LogLevelDesc = "TRACE"; +import { + Containers, + VaultTestServer, + K_DEFAULT_VAULT_HTTP_PORT, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { v4 as internalIpV4 } from "internal-ip"; +import axios from "axios"; + +// test scenario +// - enroll registrar (both using default identity and vault(p256) identity) +// - register 2 client (using registrar identity) +// - enroll 1st client using default identity +// - enroll 2nd client using vault identity(p384) +// - make invoke (InitLedger) using 1st client +// - make invoke (TransferAsset) using 2nd client (p384) client +// - make query ("ReadAsset") using registrar(p256) +test("run-transaction-with-identities", async (t: Test) => { + test.onFailure(async () => { + await Containers.logDiagnostics({ logLevel }); + }); + + const ledger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + imageVersion: "2021-04-20-nodejs", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + + const tearDown = async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }; + + test.onFinish(tearDown); + await ledger.start(); + + const connectionProfile = await ledger.getConnectionProfileOrg1(); + t.ok(connectionProfile, "getConnectionProfileOrg1() out truthy OK"); + + const registrarKey = "registrar"; + const client1Key = "client-default-"; + const client2Key = "client-vault"; + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + ///// + const vaultTestContainer = new VaultTestServer({}); + await vaultTestContainer.start(); + + const ci = await Containers.getById(vaultTestContainer.containerId); + const vaultIpAddr = await internalIpV4(); + const hostPort = await Containers.getPublicPort( + K_DEFAULT_VAULT_HTTP_PORT, + ci, + ); + const vaultHost = `http://${vaultIpAddr}:${hostPort}`; + ///// + const vaultConfig: IVaultConfig = { + endpoint: vaultHost, + transitEngineMountPath: "/transit", + }; + const testToken = "myroot"; + { + // mount engine and create some test keys + const vaultHTTPClient = axios.create({ + baseURL: vaultConfig.endpoint + "/v1", + headers: { + "X-Vault-Token": testToken, + }, + }); + await vaultHTTPClient.post( + "/sys/mounts" + vaultConfig.transitEngineMountPath, + { type: "transit" }, + ); + await vaultHTTPClient.post( + vaultConfig.transitEngineMountPath + "/keys/" + registrarKey, + { + type: "ecdsa-p256", + }, + ); + await vaultHTTPClient.post( + vaultConfig.transitEngineMountPath + "/keys/" + client2Key, + { + type: "ecdsa-p384", + }, + ); + } + test.onFinish(async () => { + await vaultTestContainer.stop(); + await vaultTestContainer.destroy(); + }); + /// + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId: keychainId, + logLevel, + }); + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + const supportedIdentity: FabricSigningCredentialType[] = [ + FabricSigningCredentialType.VaultX509, + FabricSigningCredentialType.X509, + ]; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + pluginRegistry, + sshConfig: {}, + cliContainerEnv: {}, + peerBinary: "not-required", + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + supportedIdentity, + vaultConfig: vaultConfig, + }; + const plugin = new PluginLedgerConnectorFabric(pluginOptions); + t.test("with-vaultKey", async (t: Test) => { + { + // enroll registrar using default identity + await plugin.enroll( + { + keychainId: keychainId, + keychainRef: registrarKey + "-x.509", + type: FabricSigningCredentialType.X509, + }, + { + enrollmentID: "admin", + enrollmentSecret: "adminpw", + mspId: "Org1MSP", + caId: "ca.org1.example.com", + }, + ); + + const rawCert = await keychainPlugin.get(registrarKey + "-x.509"); + t.ok(rawCert); + const certData = JSON.parse(rawCert) as IIdentityData; + t.equal(certData.type, FabricSigningCredentialType.X509); + t.ok(certData.credentials.privateKey); + } + { + // enroll registrar using vault identity + await plugin.enroll( + { + keychainId: keychainId, + keychainRef: registrarKey + "-vault", + type: FabricSigningCredentialType.VaultX509, + vaultTransitKey: { + token: testToken, + keyName: registrarKey, + }, + }, + { + enrollmentID: "admin", + enrollmentSecret: "adminpw", + mspId: "Org1MSP", + caId: "ca.org1.example.com", + }, + ); + + const rawCert = await keychainPlugin.get(registrarKey + "-vault"); + t.ok(rawCert); + const certData = JSON.parse(rawCert) as IIdentityData; + t.equal(certData.type, FabricSigningCredentialType.VaultX509); + t.notok(certData.credentials.privateKey); + } + { + // register a client1 using registrar's default x509 identity + const secret = await plugin.register( + { + keychainId: keychainId, + keychainRef: registrarKey + "-x.509", + }, + { + enrollmentID: client1Key, + enrollmentSecret: "pw", + affiliation: "org1.department1", + }, + "ca.org1.example.com", + ); + t.equal(secret, "pw"); + } + { + // register a client using registrar's vault identity + const secret = await plugin.register( + { + keychainId: keychainId, + keychainRef: registrarKey + "-vault", + type: FabricSigningCredentialType.VaultX509, + vaultTransitKey: { + token: testToken, + keyName: registrarKey, + }, + }, + { + enrollmentID: client2Key, + enrollmentSecret: "pw", + affiliation: "org1.department1", + }, + "ca.org1.example.com", + ); + t.equal(secret, "pw"); + } + + { + // enroll client client1 registered above + await plugin.enroll( + { + keychainId: keychainId, + keychainRef: client1Key, + }, + { + enrollmentID: client1Key, + enrollmentSecret: "pw", + mspId: "Org1MSP", + caId: "ca.org1.example.com", + }, + ); + + const rawCert = await keychainPlugin.get(client1Key); + t.ok(rawCert); + const certData = JSON.parse(rawCert) as IIdentityData; + t.equal(certData.type, FabricSigningCredentialType.X509); + t.ok(certData.credentials.privateKey); + } + { + // enroll client2 using vault identity + await plugin.enroll( + { + keychainId: keychainId, + keychainRef: client2Key, + type: FabricSigningCredentialType.VaultX509, + vaultTransitKey: { + token: testToken, + keyName: client2Key, + }, + }, + { + enrollmentID: client2Key, + enrollmentSecret: "pw", + mspId: "Org1MSP", + caId: "ca.org1.example.com", + }, + ); + + const rawCert = await keychainPlugin.get(client2Key); + t.ok(rawCert, "rawCert truthy OK"); + const { type, credentials } = JSON.parse(rawCert) as IIdentityData; + const { privateKey } = credentials; + t.equal(type, FabricSigningCredentialType.VaultX509, "Cert is X509 OK"); + t.notok(privateKey, "certData.credentials.privateKey falsy OK"); + } + + // Temporary workaround here: Deploy a second contract because the default + // one is being hammered with "InitLedger" transactions by the container's + // own healthcheck (see healthcheck.sh in the fabric-all-in-one folder). + // The above makes it so that transactions are triggering multiversion + // concurrency control errors. + // Deploying a fresh new contract here as a quick workaround resolves that + // problem, the real fix is to make the health check use a tx that does not + // commit instead just reads something which should still prove that the + // AIO legder is up and running fine but it won't cause this issue anymore. + const contractName = "basic2"; + const cmd = [ + "./network.sh", + "deployCC", + "-ccn", + contractName, + "-ccp", + "../asset-transfer-basic/chaincode-go", + "-ccl", + "go", + ]; + + const container = ledger.getContainer(); + const timeout = 180000; // 3 minutes + const cwd = "/fabric-samples/test-network/"; + const out = await Containers.exec(container, cmd, timeout, logLevel, cwd); + t.ok(out, "deploy Basic2 command output truthy OK"); + t.comment("Output of Basic2 contract deployment below:"); + t.comment(out); + { + // make invoke InitLedger using a client1 client + const resp = await plugin.transact({ + signingCredential: { + keychainId: keychainId, + keychainRef: client1Key, + }, + channelName: "mychannel", + contractName, + invocationType: FabricContractInvocationType.Send, + methodName: "InitLedger", + params: [], + }); + t.true(resp.success, "InitLedger tx for Basic2 success===true OK"); + } + { + // make invoke TransferAsset using a client2 client + const resp = await plugin.transact({ + signingCredential: { + keychainId: keychainId, + keychainRef: client2Key, + type: FabricSigningCredentialType.VaultX509, + vaultTransitKey: { + token: testToken, + keyName: client2Key, + }, + }, + channelName: "mychannel", + contractName, + invocationType: FabricContractInvocationType.Send, + methodName: "TransferAsset", + params: ["asset1", "client2"], + }); + t.true(resp.success, "TransferAsset asset1 client2 success true OK"); + } + { + // make query ReadAsset using a registrar client + const resp = await plugin.transact({ + signingCredential: { + keychainId: keychainId, + keychainRef: registrarKey + "-vault", + type: FabricSigningCredentialType.VaultX509, + vaultTransitKey: { + token: testToken, + keyName: registrarKey, + }, + }, + channelName: "mychannel", + contractName, + invocationType: FabricContractInvocationType.Call, + methodName: "ReadAsset", + params: ["asset1"], + }); + t.true(resp.success); + const asset = JSON.parse(resp.functionOutput); + t.equal(asset.owner, "client2"); + } + t.end(); + }); + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/identity-client.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/identity-client.test.ts new file mode 100644 index 0000000000..3d1b5513f6 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/identity-client.test.ts @@ -0,0 +1,203 @@ +import axios from "axios"; +import { v4 as internalIpV4 } from "internal-ip"; +import { + Containers, + VaultTestServer, + K_DEFAULT_VAULT_HTTP_PORT, +} from "@hyperledger/cactus-test-tooling"; +import test, { Test } from "tape-promise/tape"; +import { InternalIdentityClient } from "../../../main/typescript/identity/internal/client"; +import { VaultTransitClient } from "../../../main/typescript/identity/vault-client"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import { createHash } from "crypto"; +import { ECCurveType } from "../../../main/typescript/identity/internal/crypto-util"; +import { KJUR } from "jsrsasign"; + +const logLevel: LogLevelDesc = "TRACE"; +// a generic test suite for testing all the identity clients +// supported by this package +test("identity-clients", async (t: Test) => { + const testClients: Map = new Map(); + const testECP256 = "test-ec-p256"; + const testECP384 = "test-ec-p384"; + const testNotFoundKey = "keyNotFound"; + { + // setup vault client + const vaultTestContainer = new VaultTestServer({}); + await vaultTestContainer.start(); + + const ci = await Containers.getById(vaultTestContainer.containerId); + const vaultIpAddr = await internalIpV4(); + const hostPort = await Containers.getPublicPort( + K_DEFAULT_VAULT_HTTP_PORT, + ci, + ); + const vaultHost = `http://${vaultIpAddr}:${hostPort}`; + test.onFinish(async () => { + await vaultTestContainer.stop(); + await vaultTestContainer.destroy(); + }); + const mountPath = "/transit"; + const testToken = "myroot"; + // mount transit secret engine + await axios.post( + vaultHost + "/v1/sys/mounts" + mountPath, + { + type: "transit", + }, + { + headers: { + "X-Vault-Token": testToken, + }, + }, + ); + await axios.post( + vaultHost + "/v1" + mountPath + "/keys/" + testECP256, + { + type: "ecdsa-p256", + }, + { + headers: { + "X-Vault-Token": testToken, + }, + }, + ); + await axios.post( + vaultHost + "/v1" + mountPath + "/keys/" + testECP384, + { + type: "ecdsa-p384", + }, + { + headers: { + "X-Vault-Token": testToken, + }, + }, + ); + testClients.set( + "vault-client", + new VaultTransitClient({ + endpoint: vaultHost, + mountPath: mountPath, + token: testToken, + logLevel: logLevel, + }), + ); + } + // + for (const [clientName, client] of testClients) { + const digest = Buffer.from("Hello Cactus"); + const hashDigest = createHash("sha256").update(digest).digest(); + t.test(`${clientName}::sign`, async (t: Test) => { + { + const { sig, crv } = await client.sign(testECP256, hashDigest); + t.equal(crv, ECCurveType.P256); + t.ok(sig); + { + // asn1 encoding check + const pSig = (KJUR.crypto.ECDSA as any).parseSigHexInHexRS( + sig.toString("hex"), + ) as { r: string; s: string }; + const re = /[0-9A-Fa-f]{6}/g; + t.true(re.test(pSig.r)); + t.true(re.test(pSig.s)); + } + + { + // signature verification + const pub = await client.getPub(testECP256); + const verify = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" }); + verify.init(pub); + verify.updateHex(digest.toString("hex")); + t.true(verify.verify(sig.toString("hex"))); + } + } + { + const { sig, crv } = await client.sign(testECP384, hashDigest); + t.equal(crv, ECCurveType.P384); + t.ok(sig); + { + // asn1 encoding check + const pSig = (KJUR.crypto.ECDSA as any).parseSigHexInHexRS( + sig.toString("hex"), + ) as { r: string; s: string }; + const re = /[0-9A-Fa-f]{6}/g; + t.true(re.test(pSig.r)); + t.true(re.test(pSig.s)); + } + + { + // signature verification + const pub = await client.getPub(testECP384); + const verify = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" }); + verify.init(pub); + verify.updateHex(digest.toString("hex")); + t.true(verify.verify(sig.toString("hex"))); + } + } + t.end(); + }); + t.test(`${clientName}::getPub`, async (t: Test) => { + { + const pub = await client.getPub(testECP256); + t.ok(pub); + t.equal((pub as any).curveName, "secp256r1"); + } + { + const pub = await client.getPub(testECP384); + t.ok(pub); + t.equal((pub as any).curveName, "secp384r1"); + } + { + try { + await client.getPub(testNotFoundKey); + t.fail("Should not get here"); + } catch (error) { + t.true( + (error as Error).message.includes( + `keyName = ${testNotFoundKey} not found`, + ), + ); + } + } + t.end(); + }); + t.test(`${clientName}::rotateKey`, async (t: Test) => { + { + const pubOld = await client.getPub(testECP256); + await client.rotateKey(testECP256); + const pubNew = await client.getPub(testECP256); + { + // public key should be different + t.notEqual(pubNew.getPublicKeyXYHex(), pubOld.getPublicKeyXYHex()); + } + { + // signature should be made using new key + const { sig } = await client.sign(testECP256, hashDigest); + const verify = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" }); + verify.init(pubOld); + verify.updateHex(digest.toString("hex")); + t.false(verify.verify(sig.toString("hex"))); + } + } + { + const pubOld = await client.getPub(testECP384); + await client.rotateKey(testECP384); + const pubNew = await client.getPub(testECP384); + { + // public key should be different + t.notEqual(pubNew.getPublicKeyXYHex(), pubOld.getPublicKeyXYHex()); + } + { + // signature should be made using new key + const { sig } = await client.sign(testECP384, hashDigest); + const verify = new KJUR.crypto.Signature({ alg: "SHA256withECDSA" }); + verify.init(pubOld); + verify.updateHex(digest.toString("hex")); + t.false(verify.verify(sig.toString("hex"))); + } + } + t.end(); + }); + } + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/unit/identity-internal-crypto-utils.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/unit/identity-internal-crypto-utils.test.ts new file mode 100644 index 0000000000..ab4ff9338a --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/unit/identity-internal-crypto-utils.test.ts @@ -0,0 +1,81 @@ +import { KEYUTIL, KJUR } from "jsrsasign"; +import test, { Test } from "tape"; +import { + CryptoUtil, + ECCurveType, +} from "../../../main/typescript/identity/internal/crypto-util"; +const KJ = KJUR as any; +test("cryptoUtil", (t: Test) => { + t.test("encodeASN1Sig", (t: Test) => { + { + const asn1Sig = + "MEYCIQDb+euisbUGQCpisQh9xEKof8zVNorerfhMHv3kWmuCfQIhAI8A3f21hAHga0WK6lS6cD/ZUtWVy/xsYZGGaldM7Tl3"; + const derSig = CryptoUtil.encodeASN1Sig( + Buffer.from(asn1Sig, "base64"), + ECCurveType.P256, + ); + const want = + "3045022100dbf9eba2b1b506402a62b1087dc442a87fccd5368adeadf84c1efde45a6b827d022070ff22014a7bfe2094ba7515ab458fbfe3942517db1b32236233606baf75ebda"; + t.equal(derSig.toString("hex"), want); + } + + { + const asn1Sig = + "MGYCMQDFEvlhmQlTj7YGTjnZwERmj+/0IA2rDdb7F5iE1QLGQUUlHn353mizpFeXAcjWGH4CMQDZ1td1ISnjAPkUlohwjiJAShtaFITLG1NEr0G29Hgglt0mfvgJ0k2DXXy+mOyn57o="; + const derSig = CryptoUtil.encodeASN1Sig( + Buffer.from(asn1Sig, "base64"), + ECCurveType.P384, + ); + const want = + "3065023100c512f9619909538fb6064e39d9c044668feff4200dab0dd6fb179884d502c64145251e7df9de68b3a4579701c8d6187e02302629288aded61cff06eb69778f71ddbfb5e4a5eb7b34e4ac82b40bcaffbf0d487af38eba3ede59f78f6f5ad1e01d41b9"; + t.equal(derSig.toString("hex"), want); + } + { + const asn1Sig = + "MGYCMQDFEvlhmQlTj7YGTjnZwERmj+/0IA2rDdb7F5iE1QLGQUUlHn353mizpFeXAcjWGH4CMQDZ1td1ISnjAPkUlohwjiJAShtaFITLG1NEr0G29Hgglt0mfvgJ0k2DXXy+mOyn57o="; + try { + CryptoUtil.encodeASN1Sig( + Buffer.from(asn1Sig, "base64"), + "invalidCrv" as ECCurveType, + ); + } catch (error) { + t.equal( + "CryptoUtil#encodeASN1Sig invalid ec curve type", + (error as Error).message, + ); + } + } + t.end(); + }); + + t.test("CSR", (t: Test) => { + const pem = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEziQUVVrao4nXmhe3jaMdsxAyszHY +GfNTZZQn1F9PQCxOequ4XS4XFmng3MD8jkP58Sak/6QaXYvqAEB6pBT/gA== +-----END PUBLIC KEY----- +`; + const csr = CryptoUtil.createCSR( + KEYUTIL.getKey(pem) as KJUR.crypto.ECDSA, + "Cactus", + ); + t.ok(csr); + const csrDigest = CryptoUtil.getCSRDigest(csr); + t.equal( + csrDigest.toString("base64"), + "S5E8XQhxbltjJLE2n3krEOC5cgmENCKtvUrj3AX4StY=", + ); + const signature = Buffer.from( + "MEYCIQDzWXNQkzf4DO2Ds7MJ4RdIdQfIGbsRpK5iQAmRWyQvpAIhAKVHJL2yFIQba/S09XccNCEZhfZW3XvFqY54rz4ZIjpV", + "base64", + ); + const csrPem = CryptoUtil.getPemCSR(csr, signature); + // console.log(KJUR.asn1.csr.CSRUtil); + { + const csr = KJ.asn1.csr.CSRUtil.getParam(csrPem); + console.log(csr); + t.equal("/CN=Cactus", csr.subject.str); + } + t.end(); + }); + t.end(); +}); diff --git a/packages/cactus-test-tooling/src/main/typescript/common/containers.ts b/packages/cactus-test-tooling/src/main/typescript/common/containers.ts index 888fe3f4b2..140a2f9e5c 100644 --- a/packages/cactus-test-tooling/src/main/typescript/common/containers.ts +++ b/packages/cactus-test-tooling/src/main/typescript/common/containers.ts @@ -308,6 +308,7 @@ export class Containers { cmd: string[], timeoutMs = 300000, // 5 minutes default timeout logLevel: LogLevelDesc = "INFO", + workingDir?: string, ): Promise { const fnTag = "Containers#exec()"; Checks.truthy(container, `${fnTag} container`); @@ -318,12 +319,16 @@ export class Containers { const log = LoggerProvider.getOrCreate({ label: fnTag, level: logLevel }); - const exec = await container.exec({ + const execOptions: Record = { Cmd: cmd, AttachStdout: true, AttachStderr: true, Tty: true, - }); + }; + if (workingDir) { + execOptions.WorkingDir = workingDir; + } + const exec = await container.exec(execOptions); return new Promise((resolve, reject) => { log.debug(`Calling Exec Start on Docker Engine API...`); diff --git a/yarn.lock b/yarn.lock index 68dd24b424..859850da1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3776,6 +3776,11 @@ dependencies: "@types/node" "*" +"@types/jsrsasign@8.0.13": + version "8.0.13" + resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-8.0.13.tgz#770c1e429107dfb0cc4f5b78b472584511a55a28" + integrity sha512-+0Ij59D6NXP48KkeLhPXeQKOyLjvA9CD7zacc0Svy2IWHdl62BmDeTvGSIwKaGZSoamLJOo+on1AG/wPRLsd7A== + "@types/keyv@*": version "3.1.2" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.2.tgz#5d97bb65526c20b6e0845f6b0d2ade4f28604ee5" @@ -3853,6 +3858,13 @@ "@types/ssh2" "*" "@types/ssh2-streams" "*" +"@types/node-vault@0.9.13": + version "0.9.13" + resolved "https://registry.yarnpkg.com/@types/node-vault/-/node-vault-0.9.13.tgz#10d9fe5c9b53995c0ee24fcaeb11ba27241d6c50" + integrity sha512-TQ9zYIzFT4Oo6NVTISk9p4qbuWkVmrs7Rds53uc16sSvQeIC4WGp2y9BXVaKMoGkYQ7jAWlveFTPXLEzuCZffQ== + dependencies: + node-vault "*" + "@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "16.7.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.2.tgz#0465a39b5456b61a04d98bd5545f8b34be340cb7" @@ -5274,7 +5286,7 @@ bn.js@4.11.6: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: +bn.js@4.12.0, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -8998,6 +9010,16 @@ fabric-ca-client@2.2.8: url "^0.11.0" winston "^2.4.5" +fabric-ca-client@2.3.0-snapshot.49: + version "2.3.0-snapshot.49" + resolved "https://registry.yarnpkg.com/fabric-ca-client/-/fabric-ca-client-2.3.0-snapshot.49.tgz#7e3beb354c1b3a4548674e8a31707bb7a0238457" + integrity sha512-+JialKe65E5NO3nKOafXDHg5PTXbSq5kmNzuR74EOCVPQBbLzmn+Q6CUDT4V8BC1Nnez2kv2Yziwi44TLMvvpA== + dependencies: + fabric-common "2.3.0-snapshot.50" + jsrsasign "^8.0.24" + url "^0.11.0" + winston "^2.4.5" + fabric-common@2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/fabric-common/-/fabric-common-2.2.8.tgz#efdede0efd6b6362dbc6974511cdda8f24b50bbd" @@ -9017,6 +9039,44 @@ fabric-common@2.2.8: optionalDependencies: pkcs11js "^1.0.6" +fabric-common@2.3.0-snapshot.49: + version "2.3.0-snapshot.49" + resolved "https://registry.yarnpkg.com/fabric-common/-/fabric-common-2.3.0-snapshot.49.tgz#92b03ee299d131cdb61e9f710d09ec2c044195e0" + integrity sha512-CJHMwjBCdP/asH8G5X1/VuumpHdz8U0sWQzmT7fJxiLt8/0bSjPX9rkfd41p8ztK8sqjSBWTk/TZE4o08iyS6Q== + dependencies: + callsite "^1.0.0" + elliptic "^6.5.4" + fabric-protos "2.3.0-snapshot.49" + js-sha3 "^0.8.0" + jsrsasign "^8.0.24" + long "^4.0.0" + nconf "^0.11.2" + promise-settle "^0.3.0" + sjcl "^1.0.8" + winston "^2.4.5" + yn "^4.0.0" + optionalDependencies: + pkcs11js "^1.0.6" + +fabric-common@2.3.0-snapshot.50: + version "2.3.0-snapshot.50" + resolved "https://registry.yarnpkg.com/fabric-common/-/fabric-common-2.3.0-snapshot.50.tgz#37a627e7c2966306dcd237be7fb11ed336dc17c2" + integrity sha512-9M3kWVor5KBmiV+W7Ul14LLYeRiFW39V9+YjJYAiKWMdIUWJjgY8cfwBllnzlEbIe1/Ju+3h1UJZtikc1ZoBww== + dependencies: + callsite "^1.0.0" + elliptic "^6.5.4" + fabric-protos "2.3.0-snapshot.50" + js-sha3 "^0.8.0" + jsrsasign "^8.0.24" + long "^4.0.0" + nconf "^0.11.2" + promise-settle "^0.3.0" + sjcl "^1.0.8" + winston "^2.4.5" + yn "^4.0.0" + optionalDependencies: + pkcs11js "^1.0.6" + fabric-network@2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/fabric-network/-/fabric-network-2.2.8.tgz#e1532faee470f52e39abecbdeb907e248939e6d1" @@ -9027,6 +9087,16 @@ fabric-network@2.2.8: long "^4.0.0" nano "^9.0.3" +fabric-network@2.3.0-snapshot.49: + version "2.3.0-snapshot.49" + resolved "https://registry.yarnpkg.com/fabric-network/-/fabric-network-2.3.0-snapshot.49.tgz#d4bf5f3210feb7c2a866a381f8a2d8ac58af2554" + integrity sha512-rqyBWmzL+TgnpB0PDY8GeLtCiqgwKcMkeg9/I0JgqLpzf/zjMHpDHNx8ToqcBOkGtYnlAHzKCorgCVqDFCyv2g== + dependencies: + fabric-common "2.3.0-snapshot.50" + fabric-protos "2.3.0-snapshot.50" + long "^4.0.0" + nano "^9.0.3" + fabric-protos@2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/fabric-protos/-/fabric-protos-2.2.8.tgz#a41c4d87e6c301bc1064dc12b7e20933f99629bf" @@ -9036,6 +9106,24 @@ fabric-protos@2.2.8: "@grpc/proto-loader" "^0.6.2" protobufjs "^6.11.2" +fabric-protos@2.3.0-snapshot.49: + version "2.3.0-snapshot.49" + resolved "https://registry.yarnpkg.com/fabric-protos/-/fabric-protos-2.3.0-snapshot.49.tgz#650e5cbf08e67e4fece2e07dc9e563d301439bc3" + integrity sha512-zlEm/Xz1MCwEmv+ZA8p6TpOfe2oUxmTeRSM6lNAktZZCYWZDNzTFfXmG1kuYScVgsTH9/WvmVj1HHf6tWdfE1Q== + dependencies: + "@grpc/grpc-js" "^1.3.4" + "@grpc/proto-loader" "^0.6.2" + protobufjs "^6.11.2" + +fabric-protos@2.3.0-snapshot.50: + version "2.3.0-snapshot.50" + resolved "https://registry.yarnpkg.com/fabric-protos/-/fabric-protos-2.3.0-snapshot.50.tgz#d1b6721dacdca997526438dfd9b8510fc612fcab" + integrity sha512-6dRn2sXognFx21SQ9DrHOwZpYj8VphZHLES5COkDRhOnVBs/iDfMptwokcfOOdlDsvx8wmdNjnNMhdPABMDdKA== + dependencies: + "@grpc/grpc-js" "^1.3.4" + "@grpc/proto-loader" "^0.6.2" + protobufjs "^6.11.2" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -12015,6 +12103,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jsrsasign@10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.4.0.tgz#362cc787079c03a363a958c03eb68d8545ba92f7" + integrity sha512-C8qLhiAssh/b74KJpGhWuFGG9cFhJqMCVuuHXRibb3Z5vPuAW0ue0jUirpoExCdpdhv4nD3sZ1DAwJURYJTm9g== + jsrsasign@^8.0.24: version "8.0.24" resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-8.0.24.tgz#fc26bac45494caac3dd8f69c1f95847c4bda6c83" @@ -13879,7 +13972,7 @@ node-ssh@12.0.0: shell-escape "^0.2.0" ssh2 "^1.1.0" -node-vault@0.9.22: +node-vault@*, node-vault@0.9.22: version "0.9.22" resolved "https://registry.yarnpkg.com/node-vault/-/node-vault-0.9.22.tgz#052ab9b36c29d80d1ecfad61275259fe710d179e" integrity sha512-/IR+YvINFhCzxJA5x/KHUDymJerFaeqvPUE2zwceRig8yEIA41qfVKusmO6bqRGFkr/2f6CaBVp7YfabzQyteg==