diff options
Diffstat (limited to 'packages/taler-wallet-core')
9 files changed, 149 insertions, 14 deletions
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index dd8542def..81c43cf14 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -51,6 +51,21 @@ export interface TrustInfo { isAudited: boolean; } +export interface MerchantInfo { + supportsMerchantProtocolV1: boolean; + supportsMerchantProtocolV2: boolean; +} + +/** + * Interface for merchant-related operations. + */ +export interface MerchantOperations { + getMerchantInfo( + ws: InternalWalletState, + merchantBaseUrl: string, + ): Promise<MerchantInfo>; +} + /** * Interface for exchange-related operations. */ @@ -131,8 +146,11 @@ export interface InternalWalletState { initCalled: boolean; + merchantInfoCache: Record<string, MerchantInfo>; + exchangeOps: ExchangeOperations; recoupOps: RecoupOperations; + merchantOps: MerchantOperations; db: DbAccess<typeof WalletStoresV1>; http: HttpRequestLibrary; diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 483cb16c2..ff47cf30d 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -484,9 +484,15 @@ export interface WireInfo { export interface ExchangeDetailsPointer { masterPublicKey: string; + currency: string; /** + * Last observed protocol version range offered by the exchange. + */ + protocolVersionRange: string; + + /** * Timestamp when the (masterPublicKey, currency) pointer * has been updated. */ diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index a66bc2e84..75724dca7 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -273,6 +273,7 @@ export async function exportBackup( currency: dp.currency, master_public_key: dp.masterPublicKey, update_clock: dp.updateClock, + protocol_version_range: dp.protocolVersionRange, }); }); diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index e8e1de0b9..40fa4cdec 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -267,6 +267,7 @@ export async function importBackup( currency: backupExchange.currency, masterPublicKey: backupExchange.master_public_key, updateClock: backupExchange.update_clock, + protocolVersionRange: backupExchange.protocol_version_range, }, permanent: true, retryInfo: initRetryInfo(), diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index c170c5469..638af813a 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -23,7 +23,6 @@ import { canonicalizeBaseUrl, codecForExchangeKeysJson, codecForExchangeWireJson, - compare, Denomination, Duration, durationFromSpec, @@ -40,6 +39,7 @@ import { TalerErrorDetails, Timestamp, hashDenomPub, + LibtoolVersion, } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; import { CryptoApi } from "../crypto/workers/cryptoApi.js"; @@ -365,7 +365,10 @@ async function downloadKeysInfo( const protocolVersion = exchangeKeysJson.version; - const versionRes = compare(WALLET_EXCHANGE_PROTOCOL_VERSION, protocolVersion); + const versionRes = LibtoolVersion.compare( + WALLET_EXCHANGE_PROTOCOL_VERSION, + protocolVersion, + ); if (versionRes?.compatible != true) { const opErr = makeErrorDetails( TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE, @@ -548,6 +551,7 @@ async function updateExchangeFromUrlImpl( masterPublicKey: details.masterPublicKey, // FIXME: only change if pointer really changed updateClock: getTimestampNow(), + protocolVersionRange: keysInfo.protocolVersion, }; await tx.exchanges.put(r); await tx.exchangeDetails.put(details); diff --git a/packages/taler-wallet-core/src/operations/merchants.ts b/packages/taler-wallet-core/src/operations/merchants.ts new file mode 100644 index 000000000..d12417c7c --- /dev/null +++ b/packages/taler-wallet-core/src/operations/merchants.ts @@ -0,0 +1,68 @@ +/* + This file is part of GNU Taler + (C) 2021 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 { + canonicalizeBaseUrl, + Logger, + URL, + codecForMerchantConfigResponse, + LibtoolVersion, +} from "@gnu-taler/taler-util"; +import { InternalWalletState, MerchantInfo } from "../common.js"; +import { readSuccessResponseJsonOrThrow } from "../index.js"; + +const logger = new Logger("taler-wallet-core:merchants.ts"); + +export async function getMerchantInfo( + ws: InternalWalletState, + merchantBaseUrl: string, +): Promise<MerchantInfo> { + const canonBaseUrl = canonicalizeBaseUrl(merchantBaseUrl); + + const existingInfo = ws.merchantInfoCache[canonBaseUrl]; + if (existingInfo) { + return existingInfo; + } + + const configUrl = new URL("config", canonBaseUrl); + const resp = await ws.http.get(configUrl.href); + + const configResp = await readSuccessResponseJsonOrThrow( + resp, + codecForMerchantConfigResponse(), + ); + + logger.info( + `merchant "${canonBaseUrl}" reports protocol ${configResp.version}"`, + ); + + const merchantInfo: MerchantInfo = { + supportsMerchantProtocolV1: !!LibtoolVersion.compare( + "1:0:0", + configResp.version, + )?.compatible, + supportsMerchantProtocolV2: !!LibtoolVersion.compare( + "2:0:0", + configResp.version, + )?.compatible, + }; + + ws.merchantInfoCache[canonBaseUrl] = merchantInfo; + return merchantInfo; +} diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 07ce00d2e..0253930ea 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -27,10 +27,12 @@ import { NotificationType, TipPlanchetDetail, TalerErrorCode, - codecForTipResponse, + codecForMerchantTipResponseV1, Logger, URL, DenomKeyType, + BlindedDenominationSignature, + codecForMerchantTipResponseV2, } from "@gnu-taler/taler-util"; import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js"; import { @@ -304,31 +306,57 @@ async function processTipImpl( return; } - const response = await readSuccessResponseJsonOrThrow( - merchantResp, - codecForTipResponse(), + // FIXME: Do this earlier? + const merchantInfo = await ws.merchantOps.getMerchantInfo( + ws, + tipRecord.merchantBaseUrl, ); - if (response.blind_sigs.length !== planchets.length) { + let blindedSigs: BlindedDenominationSignature[] = []; + + if (merchantInfo.supportsMerchantProtocolV2) { + const response = await readSuccessResponseJsonOrThrow( + merchantResp, + codecForMerchantTipResponseV2(), + ); + blindedSigs = response.blind_sigs.map((x) => x.blind_sig); + } else if (merchantInfo.supportsMerchantProtocolV1) { + const response = await readSuccessResponseJsonOrThrow( + merchantResp, + codecForMerchantTipResponseV1(), + ); + blindedSigs = response.blind_sigs.map((x) => ({ + cipher: DenomKeyType.Rsa, + blinded_rsa_signature: x.blind_sig, + })); + } else { + throw Error("unsupported merchant protocol version"); + } + + if (blindedSigs.length !== planchets.length) { throw Error("number of tip responses does not match requested planchets"); } const newCoinRecords: CoinRecord[] = []; - for (let i = 0; i < response.blind_sigs.length; i++) { - const blindedSig = response.blind_sigs[i].blind_sig; + for (let i = 0; i < blindedSigs.length; i++) { + const blindedSig = blindedSigs[i]; const denom = denomForPlanchet[i]; checkLogicInvariant(!!denom); const planchet = planchets[i]; checkLogicInvariant(!!planchet); - if (denom.denomPub.cipher !== 1) { + if (denom.denomPub.cipher !== DenomKeyType.Rsa) { + throw Error("unsupported cipher"); + } + + if (blindedSig.cipher !== DenomKeyType.Rsa) { throw Error("unsupported cipher"); } const denomSigRsa = await ws.cryptoApi.rsaUnblind( - blindedSig, + blindedSig.blinded_rsa_signature, planchet.blindingKey, denom.denomPub.rsa_public_key, ); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 57bd49d23..a5a8653c6 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -24,7 +24,6 @@ import { codecForTalerConfigResponse, codecForWithdrawOperationStatusResponse, codecForWithdrawResponse, - compare, durationFromSpec, ExchangeListItem, getDurationRemaining, @@ -42,6 +41,7 @@ import { WithdrawUriInfoResponse, VersionMatchResult, DenomKeyType, + LibtoolVersion, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -285,7 +285,7 @@ export async function getBankWithdrawalInfo( codecForTalerConfigResponse(), ); - const versionRes = compare( + const versionRes = LibtoolVersion.compare( WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, config.version, ); @@ -985,7 +985,7 @@ export async function getExchangeWithdrawalInfo( let versionMatch; if (exchangeDetails.protocolVersion) { - versionMatch = compare( + versionMatch = LibtoolVersion.compare( WALLET_EXCHANGE_PROTOCOL_VERSION, exchangeDetails.protocolVersion, ); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index cd2dd7f1e..44591a268 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -99,6 +99,8 @@ import { import { ExchangeOperations, InternalWalletState, + MerchantInfo, + MerchantOperations, NotificationListener, RecoupOperations, } from "./common.js"; @@ -180,6 +182,7 @@ import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, } from "./util/http.js"; +import { getMerchantInfo } from "./operations/merchants.js"; const builtinAuditors: AuditorTrustRecord[] = [ { @@ -1069,6 +1072,8 @@ class InternalWalletStateImpl implements InternalWalletState { memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); cryptoApi: CryptoApi; + merchantInfoCache: Record<string, MerchantInfo> = {}; + timerGroup: TimerGroup = new TimerGroup(); latch = new AsyncCondition(); stopped = false; @@ -1088,6 +1093,10 @@ class InternalWalletStateImpl implements InternalWalletState { processRecoupGroup: processRecoupGroup, }; + merchantOps: MerchantOperations = { + getMerchantInfo: getMerchantInfo, + }; + /** * Promises that are waiting for a particular resource. */ |