diff options
7 files changed, 234 insertions, 81 deletions
diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index 4d6e73671..0385658ca 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -692,7 +692,7 @@ export interface FreshCoin { bks: Uint8Array; } -function bufferForUint32(n: number): Uint8Array { +export function bufferForUint32(n: number): Uint8Array { const arrBuf = new ArrayBuffer(4); const buf = new Uint8Array(arrBuf); const dv = new DataView(arrBuf); @@ -700,37 +700,6 @@ function bufferForUint32(n: number): Uint8Array { return buf; } -export function setupRefreshPlanchet( - transferSecret: Uint8Array, - coinNumber: number, -): FreshCoin { - // 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, - coinPriv, - coinPub: eddsaGetPublic(coinPriv), - }; -} - export function setupWithdrawPlanchet( secretSeed: Uint8Array, coinNumber: number, @@ -786,10 +755,10 @@ export function setupRefreshTransferPub( } /** - * + * * @param paytoUri - * @param salt 16-byte salt - * @returns + * @param salt 16-byte salt + * @returns */ export function hashWire(paytoUri: string, salt: string): string { const r = kdf( diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index 3b79f78b8..254dadf93 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -64,7 +64,10 @@ import { runBench1 } from "./bench1.js"; import { runEnv1 } from "./env1.js"; import { GlobalTestState, runTestWithState } from "./harness/harness.js"; import { runBench2 } from "./bench2.js"; -import { TalerCryptoInterface, TalerCryptoInterfaceR } from "@gnu-taler/taler-wallet-core/src/crypto/cryptoImplementation"; +import { + TalerCryptoInterface, + TalerCryptoInterfaceR, +} from "@gnu-taler/taler-wallet-core/src/crypto/cryptoImplementation"; // This module also serves as the entry point for the crypto // thread worker, and thus must expose these two handlers. @@ -75,6 +78,12 @@ export { const logger = new Logger("taler-wallet-cli.ts"); +process.on("unhandledRejection", (error: any) => { + logger.error("unhandledRejection", error.message); + logger.error("stack", error.stack); + process.exit(2); +}); + const defaultWalletDbPath = os.homedir + "/" + ".talerwalletdb.json"; function assertUnreachable(x: never): never { @@ -218,6 +227,7 @@ async function withWallet<T>( } finally { logger.info("operation with wallet finished, stopping"); wallet.stop(); + logger.info("stopped wallet"); } } @@ -250,13 +260,18 @@ walletCli console.error("Invalid JSON"); process.exit(1); } - const resp = await wallet.ws.handleCoreApiRequest( - args.api.operation, - "reqid-1", - requestJson, - ); - console.log(JSON.stringify(resp, undefined, 2)); + try { + const resp = await wallet.ws.handleCoreApiRequest( + args.api.operation, + "reqid-1", + requestJson, + ); + console.log(JSON.stringify(resp, undefined, 2)); + } catch (e) { + logger.error(`Got exception while handling API request ${e}`); + } }); + logger.info("finished handling API request"); }); walletCli 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<ValidationResult>; + rsaBlind(req: RsaBlindRequest): Promise<RsaBlindResponse>; + signDepositPermission( depositInfo: DepositInfo, ): Promise<CoinDepositPermission>; @@ -154,6 +155,14 @@ export interface TalerCryptoInterface { signCoinLink(req: SignCoinLinkRequest): Promise<EddsaSigningResult>; makeSyncSignature(req: MakeSyncSignatureRequest): Promise<EddsaSigningResult>; + + setupRefreshPlanchet( + req: SetupRefreshPlanchetRequest, + ): Promise<FreshCoinEncoded>; + + keyExchangeEcdheEddsa( + req: KeyExchangeEcdheEddsaRequest, + ): Promise<KeyExchangeResult>; } /** @@ -257,6 +266,19 @@ export const nullCrypto: TalerCryptoInterface = { ): Promise<EddsaSigningResult> { throw new Error("Function not implemented."); }, + setupRefreshPlanchet: function ( + req: SetupRefreshPlanchetRequest, + ): Promise<FreshCoinEncoded> { + throw new Error("Function not implemented."); + }, + rsaBlind: function (req: RsaBlindRequest): Promise<RsaBlindResponse> { + throw new Error("Function not implemented."); + }, + keyExchangeEcdheEddsa: function ( + req: KeyExchangeEcdheEddsaRequest, + ): Promise<KeyExchangeResult> { + throw new Error("Function not implemented."); + }, }; export type WithArg<X> = 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<RsaBlindResponse> { + const res = rsaBlind( + decodeCrock(req.hm), + decodeCrock(req.bks), + decodeCrock(req.pub), + ); + return { + blinded: encodeCrock(res), + }; + }, + + async setupRefreshPlanchet( + tci: TalerCryptoInterfaceR, + req: SetupRefreshPlanchetRequest, + ): Promise<FreshCoinEncoded> { + 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<KeyExchangeResult> { + 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, |