diff options
author | Florian Dold <florian@dold.me> | 2022-10-05 12:52:49 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-10-05 12:52:49 +0200 |
commit | dd14e67c70cd7b5b6891295759cb08aa2f94f180 (patch) | |
tree | 296b07938b676d21ba9f75196cae621e292fc714 | |
parent | 4d232fd56510ae69b76fef4f4ecade1e0d4b230d (diff) |
wallet-core: improve crypto worker code duplication
Also add new testCrypto call for later testing
12 files changed, 182 insertions, 180 deletions
diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts index a66ae8dbe..ee6a7a166 100644 --- a/packages/taler-util/src/taler-error-codes.ts +++ b/packages/taler-util/src/taler-error-codes.ts @@ -153,6 +153,14 @@ export enum TalerErrorCode { /** + * The reserve public key given as part of a /reserves/ endpoint was malformed. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_RESERVE_PUB_MALFORMED = 27, + + + /** * The currencies involved in the operation do not match. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -457,14 +465,6 @@ export enum TalerErrorCode { /** - * The reserve public key was malformed. - * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). - * (A value of 0 indicates that the error is generated client-side). - */ - EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED = 1019, - - - /** * The time at the server is too far off from the time specified in the request. Most likely the client system time is wrong. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -1289,6 +1289,38 @@ export enum TalerErrorCode { /** + * The signature by the reserve affirming the open operation is invalid. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE = 1785, + + + /** + * The signature by the reserve affirming the close operation is invalid. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE = 1786, + + + /** + * The signature by the reserve affirming the attestion request is invalid. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE = 1787, + + + /** + * The exchange does not know an origin account to which the remaining reserve balance could be wired to, and the wallet failed to provide one. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT = 1788, + + + /** * The auditor that was supposed to be disabled is unknown to this exchange. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -1769,14 +1801,6 @@ export enum TalerErrorCode { /** - * The reserve key of given to a /reserves/ handler was malformed. - * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). - * (A value of 0 indicates that the error is generated client-side). - */ - MERCHANT_GENERIC_RESERVE_PUB_MALFORMED = 2002, - - - /** * The proposal is not known to the backend. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -3065,6 +3089,14 @@ export enum TalerErrorCode { /** + * The crypto worker received a bad request. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_CRYPTO_WORKER_BAD_REQUEST = 7024, + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts index 8568f0db8..85f9acddc 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts @@ -29,7 +29,7 @@ import { timer, performanceNow, TimerHandle } from "../../util/timer.js"; import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js"; -const logger = new Logger("cryptoApi.ts"); +const logger = new Logger("cryptoDispatcher.ts"); /** * State of a crypto worker. @@ -238,7 +238,7 @@ export class CryptoDispatcher { } handleWorkerMessage(ws: WorkerInfo, msg: any): void { - const id = msg.data.id; + const id = msg.id; if (typeof id !== "number") { logger.error("rpc id must be number"); return; @@ -256,12 +256,12 @@ export class CryptoDispatcher { if (currentWorkItem.state === WorkItemState.Running) { this.numBusy--; currentWorkItem.state = WorkItemState.Finished; - if (msg.data.type === "success") { - currentWorkItem.resolve(msg.data.result); - } else if (msg.data.type === "error") { + if (msg.type === "success") { + currentWorkItem.resolve(msg.result); + } else if (msg.type === "error") { currentWorkItem.reject( TalerError.fromDetail(TalerErrorCode.WALLET_CRYPTO_WORKER_ERROR, { - innerError: msg.data.error, + innerError: msg.error, }), ); } else { diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts index 9f3ee6f50..b3620e950 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts @@ -1,8 +1,65 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { TalerErrorDetail } from "@gnu-taler/taler-util"; + +/** + * Common interface for all crypto workers. + */ export interface CryptoWorker { postMessage(message: any): void; - terminate(): void; - onmessage: ((m: any) => void) | undefined; onerror: ((m: any) => void) | undefined; } + +/** + * Type of requests sent to the crypto worker. + */ +export type CryptoWorkerRequestMessage = { + /** + * Operation ID to correlate request with the response. + */ + id: number; + + /** + * Operation to execute. + */ + operation: string; + + /** + * Operation-specific request payload. + */ + req: any; +}; + +/** + * Type of messages sent back by the crypto worker. + */ +export type CryptoWorkerResponseMessage = + | { + type: "success"; + id: number; + result: any; + } + | { + type: "error"; + id?: number; + error: TalerErrorDetail; + }; diff --git a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts index 71f137f29..ed67d2e95 100644 --- a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts @@ -17,12 +17,12 @@ /** * Imports */ -import { CryptoWorkerFactory } from "./cryptoDispatcher.js"; -import { CryptoWorker } from "./cryptoWorkerInterface.js"; -import os from "os"; import { Logger } from "@gnu-taler/taler-util"; +import os from "os"; import { nativeCryptoR } from "../cryptoImplementation.js"; -import { getErrorDetailFromException } from "../../errors.js"; +import { CryptoWorkerFactory } from "./cryptoDispatcher.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; +import { processRequestWithImpl } from "./worker-common.js"; const logger = new Logger("nodeThreadWorker.ts"); @@ -71,44 +71,7 @@ const workerCode = ` */ export function handleWorkerMessage(msg: any): void { const handleRequest = async (): Promise<void> => { - const req = msg.req; - if (typeof req !== "object") { - logger.error("request must be an object"); - return; - } - const id = msg.id; - if (typeof id !== "number") { - logger.error("RPC id must be number"); - return; - } - const operation = msg.operation; - if (typeof operation !== "string") { - logger.error("RPC operation must be string"); - return; - } - const impl = nativeCryptoR; - - if (!(operation in impl)) { - logger.error(`crypto operation '${operation}' not found`); - return; - } - - let responseMsg: any; - - try { - const result = await (impl as any)[operation](impl, req); - responseMsg = { data: { type: "success", result, id } }; - } catch (e: any) { - logger.error(`error during operation: ${e.stack ?? e.toString()}`); - responseMsg = { - data: { - type: "error", - error: getErrorDetailFromException(e), - id, - }, - }; - } - + const responseMsg = await processRequestWithImpl(msg, nativeCryptoR); try { // eslint-disable-next-line @typescript-eslint/no-var-requires const _r = "require"; @@ -122,16 +85,12 @@ export function handleWorkerMessage(msg: any): void { logger.error("parent port not available (not running in thread?"); } } catch (e: any) { - logger.error( - `error sending back operation result: ${e.stack ?? e.toString()}`, - ); + logger.error(`error in node worker: ${e.stack ?? e.toString()}`); return; } }; - handleRequest().catch((e) => { - logger.error("error in node worker", e); - }); + handleRequest(); } export function handleWorkerError(e: Error): void { diff --git a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts b/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts index a8df8b4c6..f3a4fff1c 100644 --- a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts +++ b/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts @@ -24,6 +24,9 @@ import { OpenedPromise, openPromise } from "../../util/promiseUtils.js"; const logger = new Logger("synchronousWorkerFactory.ts"); +/** + * Client for the crypto helper process (taler-crypto-worker from exchange.git). + */ export class CryptoRpcClient { proc: child_process.ChildProcessByStdio< internal.Writable, diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts index f3cfc5ef9..28ff1860b 100644 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts +++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerNode.ts @@ -14,20 +14,24 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Logger } from "@gnu-taler/taler-util"; -import { getErrorDetailFromException } from "../../errors.js"; +import { j2s, Logger } from "@gnu-taler/taler-util"; import { nativeCryptoR, TalerCryptoInterfaceR, } from "../cryptoImplementation.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; import { CryptoRpcClient } from "./rpcClient.js"; +import { processRequestWithImpl } from "./worker-common.js"; const logger = new Logger("synchronousWorker.ts"); /** * Worker implementation that uses node subprocesses. + * + * The node cryto worker can also use IPC to offload cryptographic + * operations to a helper process (ususally written in C / part of taler-exchange). */ -export class SynchronousCryptoWorker { +export class SynchronousCryptoWorker implements CryptoWorker { /** * Function to be called when we receive a message from the worker thread. */ @@ -144,61 +148,19 @@ export class SynchronousCryptoWorker { } } - private async handleRequest( - operation: string, - id: number, - req: unknown, - ): Promise<void> { - const impl = this.cryptoImplR; - - if (!(operation in impl)) { - logger.error(`crypto operation '${operation}' not found`); - return; - } - - let responseMsg: any; - try { - const result = await (impl as any)[operation](impl, req); - responseMsg = { data: { type: "success", result, id } }; - } catch (e: any) { - logger.error(`error during operation: ${e.stack ?? e.toString()}`); - responseMsg = { - data: { - type: "error", - id, - error: getErrorDetailFromException(e), - }, - }; - } - - try { - setTimeout(() => this.dispatchMessage(responseMsg), 0); - } catch (e) { - logger.error("got error during dispatch", e); - } - } - /** * Send a message to the worker thread. */ postMessage(msg: any): void { - const req = msg.req; - if (typeof req !== "object") { - logger.error("request must be an object"); - return; - } - const id = msg.id; - if (typeof id !== "number") { - logger.error("RPC id must be number"); - return; - } - const operation = msg.operation; - if (typeof operation !== "string") { - logger.error("RPC operation must be string"); - return; - } - - this.handleRequest(operation, id, req).catch((e) => { + const handleRequest = async () => { + const responseMsg = await processRequestWithImpl(msg, this.cryptoImplR); + try { + setTimeout(() => this.dispatchMessage(responseMsg), 0); + } catch (e) { + logger.error("got error during dispatch", e); + } + }; + handleRequest().catch((e) => { logger.error("Error while handling crypto request:", e); }); } diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerWeb.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerWeb.ts index 22fd0d96b..780f6c634 100644 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerWeb.ts +++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerWeb.ts @@ -14,19 +14,26 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +/** + * Imports. + */ import { Logger } from "@gnu-taler/taler-util"; -import { getErrorDetailFromException } from "../../errors.js"; import { nativeCryptoR, TalerCryptoInterfaceR, } from "../cryptoImplementation.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; +import { + processRequestWithImpl, +} from "./worker-common.js"; const logger = new Logger("synchronousWorker.ts"); /** - * Worker implementation that uses node subprocesses. + * Worker implementation that synchronously executes cryptographic + * operations. */ -export class SynchronousCryptoWorker { +export class SynchronousCryptoWorker implements CryptoWorker { /** * Function to be called when we receive a message from the worker thread. */ @@ -65,61 +72,22 @@ export class SynchronousCryptoWorker { } } - private async handleRequest( - operation: string, - id: number, - req: unknown, - ): Promise<void> { - const impl = this.cryptoImplR; - - if (!(operation in impl)) { - logger.error(`crypto operation '${operation}' not found`); - return; - } - - let responseMsg: any; - try { - const result = await (impl as any)[operation](impl, req); - responseMsg = { data: { type: "success", result, id } }; - } catch (e: any) { - logger.error(`error during operation: ${e.stack ?? e.toString()}`); - responseMsg = { - data: { - type: "error", - id, - error: getErrorDetailFromException(e), - }, - }; - } - - try { - setTimeout(() => this.dispatchMessage(responseMsg), 0); - } catch (e) { - logger.error("got error during dispatch", e); - } - } - /** * Send a message to the worker thread. */ postMessage(msg: any): void { - const req = msg.req; - if (typeof req !== "object") { - logger.error("request must be an object"); - return; - } - const id = msg.id; - if (typeof id !== "number") { - logger.error("RPC id must be number"); - return; - } - const operation = msg.operation; - if (typeof operation !== "string") { - logger.error("RPC operation must be string"); - return; - } - - this.handleRequest(operation, id, req).catch((e) => { + const handleRequest = async () => { + const responseMsg = await processRequestWithImpl( + msg, + this.cryptoImplR, + ); + try { + setTimeout(() => this.dispatchMessage(responseMsg), 0); + } catch (e) { + logger.error("got error during dispatch", e); + } + }; + handleRequest().catch((e) => { logger.error("Error while handling crypto request:", e); }); } diff --git a/packages/taler-wallet-core/src/errors.ts b/packages/taler-wallet-core/src/errors.ts index 60aecf7a1..f8f5cef0e 100644 --- a/packages/taler-wallet-core/src/errors.ts +++ b/packages/taler-wallet-core/src/errors.ts @@ -76,6 +76,9 @@ export interface DetailsMap { [TalerErrorCode.WALLET_CRYPTO_WORKER_ERROR]: { innerError: TalerErrorDetail; }; + [TalerErrorCode.WALLET_CRYPTO_WORKER_BAD_REQUEST]: { + detail: string; + }; } type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never; diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index de955e5c7..2860a6972 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -92,6 +92,7 @@ export enum WalletApiOperation { WithdrawTestBalance = "withdrawTestBalance", PreparePayForUri = "preparePayForUri", RunIntegrationTest = "runIntegrationTest", + CryptoTest = "cryptoTest", TestPay = "testPay", AddExchange = "addExchange", GetTransactions = "getTransactions", @@ -525,6 +526,15 @@ export type RunIntegrationTestOp = { }; /** + * Test crypto worker. + */ +export type CryptoTestOp = { + op: WalletApiOperation.CryptoTest; + request: {}; + response: any; +}; + +/** * Make withdrawal on a test deployment of the exchange * and merchant. */ @@ -639,6 +649,7 @@ export type WalletOperations = { [WalletApiOperation.AddBackupProvider]: AddBackupProviderOp; [WalletApiOperation.GetBackupInfo]: GetBackupInfoOp; [WalletApiOperation.RunIntegrationTest]: RunIntegrationTestOp; + [WalletApiOperation.CryptoTest]: CryptoTestOp; [WalletApiOperation.WithdrawTestBalance]: WithdrawTestBalanceOp; [WalletApiOperation.TestPay]: TestPayOp; [WalletApiOperation.ExportDb]: ExportDbOp; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 244bc1299..129ee458f 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1445,6 +1445,9 @@ async function dispatchRequestInternal( logger.info(`started fakebank withdrawal: ${j2s(fbResp)}`); return {}; } + case "testCrypto": { + return await ws.cryptoApi.hashString({ str: "hello world" }); + } case "clearDb": await clearDatabase(ws.db.idbHandle()); return {}; diff --git a/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts b/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts index 75ec0906c..c93097da8 100644 --- a/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts +++ b/packages/taler-wallet-webextension/src/browserCryptoWorkerFactory.ts @@ -28,6 +28,8 @@ export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory { startWorker(): CryptoWorker { const workerCtor = Worker; const workerPath = "/dist/browserWorkerEntry.js"; + // FIXME: This is not really the same interface as the crypto worker! + // We need to wrap it. return new workerCtor(workerPath) as CryptoWorker; } diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 9581abc56..68ec15591 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -217,6 +217,8 @@ async function reinitWallet(): Promise<void> { timer = new SetTimeoutTimerAPI(); } else { httpLib = new BrowserHttpLib(); + // We could (should?) use the BrowserCryptoWorkerFactory here, + // but right now we don't, to have less platform differences. cryptoWorker = new SynchronousCryptoWorkerFactory(); timer = new SetTimeoutTimerAPI(); } |