From 9d38cb56a6fa4c9a975df339cb0aa08f040368c1 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 24 Mar 2022 01:10:34 +0100 Subject: wallet: crypto worker fixes, better taler-crypto-worker integration --- .../src/crypto/cryptoImplementation.ts | 168 ++++++++++++++++++--- .../src/crypto/workers/cryptoDispatcher.ts | 8 +- .../src/crypto/workers/synchronousWorker.ts | 53 ++++++- .../taler-wallet-core/src/operations/refresh.ts | 17 ++- packages/taler-wallet-core/src/wallet.ts | 1 + 5 files changed, 208 insertions(+), 39 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index 63b2687b6..a6093e365 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -42,7 +42,6 @@ import { eddsaVerify, encodeCrock, ExchangeProtocolVersion, - FreshCoin, hash, hashCoinEv, hashCoinEvInner, @@ -53,14 +52,12 @@ import { MakeSyncSignatureRequest, PlanchetCreationRequest, WithdrawalPlanchet, - randomBytes, RecoupRefreshRequest, RecoupRequest, RefreshPlanchetInfo, rsaBlind, rsaUnblind, rsaVerify, - setupRefreshPlanchet, setupRefreshTransferPub, setupTipPlanchet, setupWithdrawPlanchet, @@ -70,6 +67,8 @@ import { UnblindedSignature, PlanchetUnblindInfo, TalerProtocolTimestamp, + kdfKw, + bufferForUint32, } from "@gnu-taler/taler-util"; import bigint from "big-integer"; import { DenominationRecord, WireFee } from "../db.js"; @@ -141,6 +140,8 @@ export interface TalerCryptoInterface { rsaVerify(req: RsaVerificationRequest): Promise; + rsaBlind(req: RsaBlindRequest): Promise; + signDepositPermission( depositInfo: DepositInfo, ): Promise; @@ -154,6 +155,14 @@ export interface TalerCryptoInterface { signCoinLink(req: SignCoinLinkRequest): Promise; makeSyncSignature(req: MakeSyncSignatureRequest): Promise; + + setupRefreshPlanchet( + req: SetupRefreshPlanchetRequest, + ): Promise; + + keyExchangeEcdheEddsa( + req: KeyExchangeEcdheEddsaRequest, + ): Promise; } /** @@ -257,6 +266,19 @@ export const nullCrypto: TalerCryptoInterface = { ): Promise { throw new Error("Function not implemented."); }, + setupRefreshPlanchet: function ( + req: SetupRefreshPlanchetRequest, + ): Promise { + throw new Error("Function not implemented."); + }, + rsaBlind: function (req: RsaBlindRequest): Promise { + throw new Error("Function not implemented."); + }, + keyExchangeEcdheEddsa: function ( + req: KeyExchangeEcdheEddsaRequest, + ): Promise { + throw new Error("Function not implemented."); + }, }; export type WithArg = X extends (req: infer T) => infer R @@ -275,12 +297,23 @@ export interface SignCoinLinkRequest { coinEv: CoinEnvelope; } +export interface SetupRefreshPlanchetRequest { + transferSecret: string; + coinNumber: number; +} + export interface RsaVerificationRequest { hm: string; sig: string; pk: string; } +export interface RsaBlindRequest { + hm: string; + bks: string; + pub: string; +} + export interface EddsaSigningResult { sig: string; } @@ -341,16 +374,35 @@ export interface UnblindDenominationSignatureRequest { evSig: BlindedDenominationSignature; } +export interface FreshCoinEncoded { + coinPub: string; + coinPriv: string; + bks: string; +} + export interface RsaUnblindRequest { blindedSig: string; bk: string; pk: string; } +export interface RsaBlindResponse { + blinded: string; +} + export interface RsaUnblindResponse { sig: string; } +export interface KeyExchangeEcdheEddsaRequest { + ecdhePriv: string; + eddsaPub: string; +} + +export interface KeyExchangeResult { + h: string; +} + export const nativeCryptoR: TalerCryptoInterfaceR = { async eddsaSign( tci: TalerCryptoInterfaceR, @@ -361,6 +413,53 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { }; }, + async rsaBlind( + tci: TalerCryptoInterfaceR, + req: RsaBlindRequest, + ): Promise { + const res = rsaBlind( + decodeCrock(req.hm), + decodeCrock(req.bks), + decodeCrock(req.pub), + ); + return { + blinded: encodeCrock(res), + }; + }, + + async setupRefreshPlanchet( + tci: TalerCryptoInterfaceR, + req: SetupRefreshPlanchetRequest, + ): Promise { + const transferSecret = decodeCrock(req.transferSecret); + const coinNumber = req.coinNumber; + // See TALER_transfer_secret_to_planchet_secret in C impl + const planchetMasterSecret = kdfKw({ + ikm: transferSecret, + outputLength: 32, + salt: bufferForUint32(coinNumber), + info: stringToBytes("taler-coin-derivation"), + }); + + const coinPriv = kdfKw({ + ikm: planchetMasterSecret, + outputLength: 32, + salt: stringToBytes("coin"), + }); + + const bks = kdfKw({ + ikm: planchetMasterSecret, + outputLength: 32, + salt: stringToBytes("bks"), + }); + + return { + bks: encodeCrock(bks), + coinPriv: encodeCrock(coinPriv), + coinPub: encodeCrock(eddsaGetPublic(coinPriv)), + }; + }, + async createPlanchet( tci: TalerCryptoInterfaceR, req: PlanchetCreationRequest, @@ -374,10 +473,14 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { req.coinIndex, ); const coinPubHash = hash(derivedPlanchet.coinPub); - const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPubRsa); + const blindResp = await tci.rsaBlind(tci, { + bks: encodeCrock(derivedPlanchet.bks), + hm: encodeCrock(coinPubHash), + pub: denomPub.rsa_public_key, + }); const coinEv: CoinEnvelope = { cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: encodeCrock(ev), + rsa_blinded_planchet: blindResp.blinded, }; const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount; const denomPubHash = hashDenomPub(req.denomPub); @@ -423,10 +526,14 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex); const denomPub = decodeCrock(req.denomPub.rsa_public_key); const coinPubHash = hash(fc.coinPub); - const ev = rsaBlind(coinPubHash, fc.bks, denomPub); + const blindResp = await tci.rsaBlind(tci, { + bks: encodeCrock(fc.bks), + hm: encodeCrock(coinPubHash), + pub: encodeCrock(denomPub), + }); const coinEv = { cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: encodeCrock(ev), + rsa_blinded_planchet: blindResp.blinded, }; const tipPlanchet: DerivedTipPlanchet = { blindingKey: encodeCrock(fc.bks), @@ -798,32 +905,32 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { const denomSel = newCoinDenoms[j]; for (let k = 0; k < denomSel.count; k++) { const coinIndex = planchets.length; - const transferPriv = decodeCrock(transferPrivs[i]); - const oldCoinPub = decodeCrock(meltCoinPub); - const transferSecret = keyExchangeEcdheEddsa( - transferPriv, - oldCoinPub, - ); + const transferSecretRes = await tci.keyExchangeEcdheEddsa(tci, { + ecdhePriv: transferPrivs[i], + eddsaPub: meltCoinPub, + }); let coinPub: Uint8Array; let coinPriv: Uint8Array; let blindingFactor: Uint8Array; - // FIXME: make setupRefreshPlanchet a crypto api fn - let fresh: FreshCoin = setupRefreshPlanchet( - transferSecret, - coinIndex, - ); - coinPriv = fresh.coinPriv; - coinPub = fresh.coinPub; - blindingFactor = fresh.bks; + let fresh: FreshCoinEncoded = await tci.setupRefreshPlanchet(tci, { + coinNumber: coinIndex, + transferSecret: transferSecretRes.h, + }); + coinPriv = decodeCrock(fresh.coinPriv); + coinPub = decodeCrock(fresh.coinPub); + blindingFactor = decodeCrock(fresh.bks); const coinPubHash = hash(coinPub); if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) { throw Error("unsupported cipher, can't create refresh session"); } - const rsaDenomPub = decodeCrock(denomSel.denomPub.rsa_public_key); - const ev = rsaBlind(coinPubHash, blindingFactor, rsaDenomPub); + const blindResult = await tci.rsaBlind(tci, { + bks: encodeCrock(blindingFactor), + hm: encodeCrock(coinPubHash), + pub: denomSel.denomPub.rsa_public_key, + }); const coinEv: CoinEnvelope = { cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: encodeCrock(ev), + rsa_blinded_planchet: blindResult.blinded, }; const coinEvHash = hashCoinEv( coinEv, @@ -921,6 +1028,19 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { const uploadSig = eddsaSign(sigBlob, decodeCrock(req.accountPriv)); return { sig: encodeCrock(uploadSig) }; }, + async keyExchangeEcdheEddsa( + tci: TalerCryptoInterfaceR, + req: KeyExchangeEcdheEddsaRequest, + ): Promise { + return { + h: encodeCrock( + keyExchangeEcdheEddsa( + decodeCrock(req.ecdhePriv), + decodeCrock(req.eddsaPub), + ), + ), + }; + }, }; function amountToBuffer(amount: AmountJson): Uint8Array { diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts index 810273cca..f5782ab09 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts @@ -122,7 +122,7 @@ export class CryptoDispatcher { worker.idleTimeoutHandle = null; } if (worker.currentWorkItem) { - worker.currentWorkItem.reject(Error("explicitly terminated")); + worker.currentWorkItem.reject(new CryptoApiStoppedError()); worker.currentWorkItem = null; } if (worker.w) { @@ -143,7 +143,7 @@ export class CryptoDispatcher { */ wake(ws: WorkerState, work: WorkItem): void { if (this.stopped) { - throw new CryptoApiStoppedError(); + return; } if (ws.currentWorkItem !== null) { throw Error("assertion failed"); @@ -331,8 +331,8 @@ export class CryptoDispatcher { } timeout.clear(); resolve(x); - }); - p.catch((x) => { + }).catch((x) => { + logger.info(`crypto RPC call ${operation} threw`); if (timedOut) { return; } diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts index 1d7539ed6..a5f154e92 100644 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts @@ -47,14 +47,11 @@ export class SynchronousCryptoWorker { this.cryptoImplR = { ...nativeCryptoR }; - if ( - process.env["TALER_WALLET_RPC_CRYPRO"] || - // Old name - process.env["TALER_WALLET_PRIMITIVE_WORKER"] - ) { + if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) { + logger.info("using RPC for some crypto operations"); const rpc = (this.rpcClient = new CryptoRpcClient()); this.cryptoImplR.eddsaSign = async (_, req) => { - logger.trace("making RPC request"); + logger.info("calling RPC impl of eddsaSign"); return await rpc.queueRequest({ op: "eddsa_sign", args: { @@ -63,6 +60,46 @@ export class SynchronousCryptoWorker { }, }); }; + this.cryptoImplR.setupRefreshPlanchet = async (_, req) => { + const res = await rpc.queueRequest({ + op: "setup_refresh_planchet", + args: { + coin_index: req.coinNumber, + transfer_secret: req.transferSecret, + }, + }); + return { + bks: res.blinding_key, + coinPriv: res.coin_priv, + coinPub: res.coin_pub, + }; + }; + this.cryptoImplR.rsaBlind = async (_, req) => { + const res = await rpc.queueRequest({ + op: "rsa_blind", + args: { + bks: req.bks, + hm: req.hm, + pub: req.pub, + }, + }); + return { + blinded: res.blinded, + }; + }; + + this.cryptoImplR.keyExchangeEcdheEddsa = async (_, req) => { + const res = await rpc.queueRequest({ + op: "kx_ecdhe_eddsa", + args: { + ecdhe_priv: req.ecdhePriv, + eddsa_pub: req.eddsaPub, + }, + }); + return { + h: res.h, + }; + }; } } @@ -101,8 +138,8 @@ export class SynchronousCryptoWorker { let result: any; try { result = await (impl as any)[operation](impl, req); - } catch (e) { - logger.error("error during operation", e); + } catch (e: any) { + logger.error(`error during operation '${operation}': ${e}`); return; } diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index a77738262..2ab06abae 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -797,11 +797,22 @@ async function processRefreshGroupImpl( return; } // Process refresh sessions of the group in parallel. + logger.trace("processing refresh sessions for old coins"); const ps = refreshGroup.oldCoinPubs.map((x, i) => - processRefreshSession(ws, refreshGroupId, i), + processRefreshSession(ws, refreshGroupId, i).catch((x) => { + logger.warn("process refresh session got exception"); + logger.warn(`exc ${x}`); + logger.warn(`exc stack ${x.stack}`); + }), ); - await Promise.all(ps); - logger.trace("refresh finished"); + try { + logger.trace("waiting for refreshes"); + await Promise.all(ps); + logger.trace("refresh finished"); + } catch (e) { + logger.warn("process refresh sessions got exception"); + logger.warn(`exception: ${e}`); + } } async function processRefreshSession( diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index d1e9aa646..bb560774a 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1077,6 +1077,7 @@ export async function handleCoreApiRequest( }; } catch (e: any) { const err = getErrorDetailFromException(e); + logger.info(`finished wallet core request with error: ${j2s(err)}`); return { type: "error", operation, -- cgit v1.2.3