diff --git a/package-lock.json b/package-lock.json index 38133bd..14432bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -382,6 +382,15 @@ "long": "^3.2.0" } }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -1782,6 +1791,21 @@ "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=" }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==" + } + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2240,6 +2264,11 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -3587,6 +3616,33 @@ "is-object": "^1.0.1" } }, + "jayson": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-2.1.0.tgz", + "integrity": "sha512-WQhCph4BgSDbRUPdZYqGMojKMxjzPqCCKmWYMsRWX/Bvh1oP+Irs2upeEJy8flU3ZAZzm68TjuL1X8u9Rt4wWQ==", + "requires": { + "@types/node": "^10.3.5", + "JSONStream": "^1.3.1", + "commander": "^2.12.2", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.10", + "uuid": "^3.2.1" + }, + "dependencies": { + "@types/node": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", + "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "js-sha3": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", @@ -3653,6 +3709,11 @@ "graceful-fs": "^4.1.6" } }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, "jsonwebtoken": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", diff --git a/package.json b/package.json index 905ba40..41a8da3 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "eth-crypto": "^1.2.4", "eth-ecies": "^1.0.3", "ethereumjs-util": "^6.0.0", + "jayson": "^2.1.0", "jsonwebtoken": "^8.3.0", "node-fetch": "^2.2.0", "web3": "1.0.0-beta.36", diff --git a/src/secretstore/ParityClient.ts b/src/secretstore/ParityClient.ts new file mode 100644 index 0000000..c040e0a --- /dev/null +++ b/src/secretstore/ParityClient.ts @@ -0,0 +1,75 @@ +import * as jayson from "jayson" +import {Client} from "jayson" +import {URL} from "url" +import Logger from "../utils/Logger" + +function add0xPrefix(key) { + return key.startsWith("0x") ? key : "0x" + key +} + +export default class ParityClient { + + private rpcClient: Client + + constructor(private url: string, private address: string, private password: string) { + this.rpcClient = jayson.Client.http(new URL(this.url)) + } + + public async signKeyId(keyId): Promise { + return this.sendJsonRpcRequest(this.rpcClient, + "secretstore_signRawHash", + [this.address, this.password, add0xPrefix(keyId)]) + .then((result: string) => { + Logger.log("fu", result) + return result + }) + + } + + public generateDocumentKeyFromKey(serverKey) { + return this.sendJsonRpcRequest(this.rpcClient, + "secretstore_generateDocumentKey", + [this.address, this.password, serverKey]) + .then((result: string) => { + return result + }) + + } + + public encryptDocument(encryptedKey, document: string) { + // `document` must be encoded in hex when sent to encryption + return this.sendJsonRpcRequest(this.rpcClient, "secretstore_encrypt", + [this.address, this.password, encryptedKey, + add0xPrefix(new Buffer(document).toString("hex"))]) + .then((result: string) => { + return result + }) + } + + public decryptDocument(decryptedSecret, commonPoint, decryptShadows, encryptedDocument) { + return this.sendJsonRpcRequest(this.rpcClient, + "secretstore_shadowDecrypt", + [this.address, this.password, decryptedSecret, + commonPoint, decryptShadows, encryptedDocument]) + .then((result: string) => { + return result + }) + } + + private sendJsonRpcRequest(rpcClient: Client, methodName: string, paramsList: any[]) { + return new Promise((resolve, reject) => { + rpcClient.request( + methodName, + paramsList, + (err, response) => { + const error = response.error || err + if (error) { + Logger.error("JSON RPC call failed:", error) + Logger.error(`Method ${methodName}`) + return reject(error) + } + return resolve(response.result.toString()) + }) + }) + } +} \ No newline at end of file diff --git a/src/secretstore/SecretStore.ts b/src/secretstore/SecretStore.ts index ed9b191..0dd5e1f 100644 --- a/src/secretstore/SecretStore.ts +++ b/src/secretstore/SecretStore.ts @@ -1,8 +1,59 @@ -import SecretStoreDocumentHandler from "@oceanprotocol/secret-store-client" +import Logger from "../utils/Logger" +import ParityClient from "./ParityClient" +import SecretStoreClient from "./SecretStoreClient" export default class SecretStore { - public test() { - return new SecretStoreDocumentHandler() + private partiyClient: ParityClient + private secretStoreClient: SecretStoreClient + + constructor(config: { secretStoreUrl: string, parityUrl: string, address: string, password: string }) { + + this.partiyClient = new ParityClient(config.parityUrl, config.address, config.password) + this.secretStoreClient = new SecretStoreClient(config.secretStoreUrl) } + + public async generateServerKey(serverKeyId: string): Promise { + + const serverKeyIdSig = await this.partiyClient.signKeyId(serverKeyId) + + Logger.log("serverKeyId:", serverKeyId, "serverKeyIdSig:", serverKeyIdSig) + + const key = await this.secretStoreClient.generateServerKey(serverKeyId, serverKeyIdSig) + + Logger.log("key:", key) + + return key + } + + public async storeDocumentKey(serverKeyId: string, documentKeyId): Promise { + + const serverKeyIdSig = await this.partiyClient.signKeyId(serverKeyId) + const documentKeyIdSig = await this.partiyClient.signKeyId(documentKeyId) + + Logger.log("serverKeyId:", serverKeyId, "serverKeyIdSig:", serverKeyIdSig) + + const key = await this.secretStoreClient.storeDocumentKey( + serverKeyId, serverKeyIdSig, + documentKeyId, documentKeyIdSig, + ) + + Logger.log("key:", key) + + return key + } + + public async retrieveDocumentKey(serverKeyId: string): Promise { + + const serverKeyIdSig = await this.partiyClient.signKeyId(serverKeyId) + + Logger.log("serverKeyId:", serverKeyId, "serverKeyIdSig:", serverKeyIdSig) + + const key = await this.secretStoreClient.retrieveDocumentKey(serverKeyId, serverKeyIdSig) + + Logger.log("key:", key) + + return key + } + } diff --git a/src/secretstore/SecretStoreClient.ts b/src/secretstore/SecretStoreClient.ts new file mode 100644 index 0000000..a2b4ce9 --- /dev/null +++ b/src/secretstore/SecretStoreClient.ts @@ -0,0 +1,108 @@ +import fetch from "node-fetch" +import Logger from "../utils/Logger" + +function removeLeading0xPrefix(key) { + return key.startsWith("0x") ? key.replace("0x", "") : key +} + +export default class SecretStoreClient { + + constructor(private url: string, private threshold?: number) { + this.url = url + this.threshold = threshold || 1 + } + + public async generateServerKey(serverKeyId: string, serverKeyIdSig: string): Promise { + + const url = [ + this.url, "shadow", serverKeyId, + removeLeading0xPrefix(serverKeyIdSig), + this.threshold, + ].join("/") + + const result = await fetch(url, { + method: "POST", + }) + .then((response) => { + if (response.ok) { + return response.json() + } + throw Error(`Unable to generate Server Key ${response.statusText}`) + }) + .catch((error) => { + throw Error(`Unable to generate Server Key: ${error.message}`) + }) + + if (!result) { + throw Error(`Unable to generate Server Key`) + } + + return result + } + + /* + curl -X POST http://localhost:8082/shadow/ + 0000000000000000000000000000000000000000000000000000000000000000/ + de12681e0b8f7a428f12a6694a5f7e1324deef3d627744d95d51b862afc13799251831b3611ae436c452b54cdf5c4e78b361a396ae183e8b4c34519e895e623c00/ + 368244efaf441c2dabf7a723355a97b3b86f27bdb2827ae6f34ddece5132efd37af4ba808957b7113b4296bc4ae9ec7be38f9de6bae00504e775883a50d4658a/ + b7ad0603946987f1a154ae7f074e45da224eaa83704aac16a2d43a675d219654cf087b5d7aacce0790a65abbc1a495b26e71a5c6e9a4a71b543bf0048935bc13 + */ + + public async storeDocumentKey(serverKeyId: string, serverKeyIdSig: string, + commonPoint: string, encryptedPoint: string) { + const url = [this.url, "shadow", serverKeyId, removeLeading0xPrefix(serverKeyIdSig), + removeLeading0xPrefix(commonPoint), removeLeading0xPrefix(encryptedPoint)] + .join("/") + + Logger.log("url", url) + const result = await fetch(url, { + method: "POST", + }) + .then((response) => { + if (response.ok) { + return response + } + throw Error(`Unable to store document Keys ${response.statusText}`) + }) + .catch((error) => { + throw Error(`Unable to store document keys: ${error.message}`) + }) + + if (!result) { + throw Error(`Unable to store document Keys`) + } + + return result + } + + public async retrieveDocumentKey(documentKeyId, documentKeyIdSig) { + + const url = [ + this.url, documentKeyId, + removeLeading0xPrefix(documentKeyIdSig), + ].join("/") + + Logger.log(url) + + const result = await fetch(url, { + method: "GET", + }) + .then((response) => { + + if (response.ok) { + return response.json() + } + throw Error(`Unable to retrieve decryption Keys ${response.statusText}`) + }) + .catch((e) => { + throw Error(`Unable to retrieve decryption keys: ${e.message}`) + }) + + if (!result) { + throw Error(`Unable to retrieve decryption Keys`) + } + + // results should have (decrypted_secret, common_point, decrypt_shadows) + return JSON.parse(result) + } +} diff --git a/test/secretstore/SecretStore.test.ts b/test/secretstore/SecretStore.test.ts deleted file mode 100644 index 9431542..0000000 --- a/test/secretstore/SecretStore.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {assert} from "chai" -import SecretStore from "../../src/secretstore/SecretStore" - -describe("SecretStore", () => { - - describe("#test()", () => { - it("should return instance", () => { - - const fu = new SecretStore().test() - assert(fu) - }) - }) -}) diff --git a/test/secretstore/SecretStore.test_.ts b/test/secretstore/SecretStore.test_.ts new file mode 100644 index 0000000..e107b55 --- /dev/null +++ b/test/secretstore/SecretStore.test_.ts @@ -0,0 +1,52 @@ +import BigNumber from "bignumber.js" +import {assert} from "chai" +import ConfigProvider from "../../src/ConfigProvider" +import Config from "../../src/models/Config" +import SecretStore from "../../src/secretstore/SecretStore" + +const parityUrl = "http://localhost:8545" +const ssUrl = "https://secret-store.dev-ocean.com" + +ConfigProvider.configure({ + nodeUri: ssUrl, +} as Config) + +const address = "0xa50f397644973dba99624404b2894825840aa03b" +const password = "unittest" + +const secretStore: SecretStore = new SecretStore({ + secretStoreUrl: ssUrl, parityUrl, + address, + password, +}) + +function generateRandomId(): string { + const id: string = BigNumber.random(64).toString().replace("0.", "") + + // sometimes it only generates 63 digits + return id.length === 63 ? id + "0" : id +} + +describe("SecretStore", () => { + + describe("#generateServerKey()", () => { + it("should generate Server key", async () => { + + const serverKeyId = generateRandomId() + const serverKey = await secretStore.generateServerKey(serverKeyId) + + assert(serverKey) + }) + }) + + describe("#storeDocumentKey()", () => { + it("should store Document key", async () => { + + const serverKeyId = generateRandomId() + const documentKeyId = generateRandomId() + const documentKey = await secretStore.storeDocumentKey(serverKeyId, documentKeyId) + + assert(documentKey) + }) + }) +})