From cb44202440313ea4405fbc74f4588144134a0821 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 12 Oct 2022 14:36:30 -0300 Subject: adding global fee info from exchange --- .../src/crypto/cryptoImplementation.ts | 62 +++++++++++++++++++++- packages/taler-wallet-core/src/db.ts | 5 ++ .../src/operations/backup/export.ts | 1 + .../src/operations/backup/import.ts | 1 + .../taler-wallet-core/src/operations/exchanges.ts | 36 +++++++++++++ 5 files changed, 103 insertions(+), 2 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 bfc48d961..98bb6c9cb 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -51,6 +51,7 @@ import { encryptContractForMerge, ExchangeProtocolVersion, getRandomBytes, + GlobalFees, hash, HashCodeString, hashCoinEv, @@ -74,6 +75,7 @@ import { rsaVerify, setupTipPlanchet, stringToBytes, + TalerProtocolDuration, TalerProtocolTimestamp, TalerSignaturePurpose, UnblindedSignature, @@ -142,6 +144,10 @@ export interface TalerCryptoInterface { isValidWireFee(req: WireFeeValidationRequest): Promise; + isValidGlobalFees( + req: GlobalFeesValidationRequest, + ): Promise; + isValidDenom(req: DenominationValidationRequest): Promise; isValidWireAccount( @@ -152,7 +158,7 @@ export interface TalerCryptoInterface { req: ContractTermsValidationRequest, ): Promise; - createEddsaKeypair(req: {}): Promise; + createEddsaKeypair(req: unknown): Promise; eddsaGetPublic(req: EddsaGetPublicRequest): Promise; @@ -283,12 +289,17 @@ export const nullCrypto: TalerCryptoInterface = { ): Promise { throw new Error("Function not implemented."); }, + isValidGlobalFees: function ( + req: GlobalFeesValidationRequest, + ): Promise { + throw new Error("Function not implemented."); + }, isValidContractTermsSignature: function ( req: ContractTermsValidationRequest, ): Promise { throw new Error("Function not implemented."); }, - createEddsaKeypair: function (req: {}): Promise { + createEddsaKeypair: function (req: unknown): Promise { throw new Error("Function not implemented."); }, eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise { @@ -484,6 +495,11 @@ export interface WireFeeValidationRequest { masterPub: string; } +export interface GlobalFeesValidationRequest { + gf: GlobalFees; + masterPub: string; +} + export interface DenominationValidationRequest { denom: DenominationRecord; masterPub: string; @@ -887,6 +903,30 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { return { valid: eddsaVerify(p, sig, pub) }; }, + /** + * Check if a global fee is correctly signed. + */ + async isValidGlobalFees( + tci: TalerCryptoInterfaceR, + req: GlobalFeesValidationRequest, + ): Promise { + const { gf, masterPub } = req; + const p = buildSigPS(TalerSignaturePurpose.GLOBAL_FEES) + .put(timestampRoundedToBuffer(gf.start_date)) + .put(timestampRoundedToBuffer(gf.end_date)) + .put(durationRoundedToBuffer(gf.purse_timeout)) + .put(durationRoundedToBuffer(gf.account_kyc_timeout)) + .put(durationRoundedToBuffer(gf.history_expiration)) + .put(amountToBuffer(Amounts.parseOrThrow(gf.history_fee))) + .put(amountToBuffer(Amounts.parseOrThrow(gf.kyc_fee))) + .put(amountToBuffer(Amounts.parseOrThrow(gf.account_fee))) + .put(amountToBuffer(Amounts.parseOrThrow(gf.purse_fee))) + .put(bufferForUint32(gf.purse_account_limit)) + .build(); + const sig = decodeCrock(gf.master_sig); + const pub = decodeCrock(masterPub); + return { valid: eddsaVerify(p, sig, pub) }; + }, /** * Check if the signature of a denomination is valid. */ @@ -1630,6 +1670,24 @@ function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array { return new Uint8Array(b); } +function durationRoundedToBuffer(ts: TalerProtocolDuration): Uint8Array { + const b = new ArrayBuffer(8); + const v = new DataView(b); + // The buffer we sign over represents the timestamp in microseconds. + if (typeof v.setBigUint64 !== "undefined") { + const s = BigInt(ts.d_us); + v.setBigUint64(0, s); + } else { + const s = ts.d_us === "forever" ? bigint.zero : bigint(ts.d_us); + const arr = s.toArray(2 ** 8).value; + let offset = 8 - arr.length; + for (let i = 0; i < arr.length; i++) { + v.setUint8(offset++, arr[i]); + } + } + return new Uint8Array(b); +} + export interface EddsaSignRequest { msg: string; priv: string; diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index ec11f4d47..e266275c1 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -45,6 +45,7 @@ import { Location, WireInfo, DenominationInfo, + GlobalFees, } from "@gnu-taler/taler-util"; import { RetryInfo, RetryTags } from "./util/retries.js"; import { Event, IDBDatabase } from "@gnu-taler/idb-bridge"; @@ -424,6 +425,10 @@ export interface ExchangeDetailsRecord { reserveClosingDelay: TalerProtocolDuration; + /** + * Fees for exchange services + */ + globalFees: GlobalFees[]; /** * Signing keys we got from the exchange, can also contain * older signing keys that are not returned by /keys anymore. diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 2e2a1c4b4..f611a2380 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -345,6 +345,7 @@ export async function exportBackup( stamp_expire: x.stamp_expire, stamp_start: x.stamp_start, })), + global_fees: ex.globalFees, tos_accepted_etag: ex.termsOfServiceAcceptedEtag, tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp, denominations: diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 3ee3680fe..ee8cb6f6c 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -405,6 +405,7 @@ export async function importBackup( masterPublicKey: backupExchangeDetails.master_public_key, protocolVersion: backupExchangeDetails.protocol_version, reserveClosingDelay: backupExchangeDetails.reserve_closing_delay, + globalFees: backupExchangeDetails.global_fees, signingKeys: backupExchangeDetails.signing_keys.map((x) => ({ key: x.key, master_sig: x.master_sig, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 9a6c72577..a26c14fcc 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -32,6 +32,7 @@ import { ExchangeDenomination, ExchangeSignKeyJson, ExchangeWireJson, + GlobalFees, hashDenomPub, j2s, LibtoolVersion, @@ -269,6 +270,32 @@ async function validateWireInfo( }; } +async function validateGlobalFees( + ws: InternalWalletState, + fees: GlobalFees[], + masterPub: string, +): Promise { + for (const gf of fees) { + logger.trace("validating exchange global fees"); + let isValid = false; + if (ws.insecureTrustExchange) { + isValid = true; + } else { + const { valid: v } = await ws.cryptoApi.isValidGlobalFees({ + masterPub, + gf, + }); + isValid = v; + } + + if (!isValid) { + throw Error("exchange global fees signature invalid: " + gf.master_sig); + } + } + + return fees; +} + export interface ExchangeInfo { wire: ExchangeWireJson; keys: ExchangeKeysDownloadResult; @@ -359,6 +386,7 @@ interface ExchangeKeysDownloadResult { expiry: TalerProtocolTimestamp; recoup: Recoup[]; listIssueDate: TalerProtocolTimestamp; + globalFees: GlobalFees[]; } /** @@ -432,6 +460,7 @@ async function downloadExchangeKeysInfo( ), recoup: exchangeKeysJsonUnchecked.recoup ?? [], listIssueDate: exchangeKeysJsonUnchecked.list_issue_date, + globalFees: exchangeKeysJsonUnchecked.global_fees, }; } @@ -552,6 +581,12 @@ export async function updateExchangeFromUrlHandler( keysInfo.masterPublicKey, ); + const globalFees = await validateGlobalFees( + ws, + keysInfo.globalFees, + keysInfo.masterPublicKey, + ); + logger.info("finished validating exchange /wire info"); const tosDownload = await downloadTosFromAcceptedFormat( @@ -594,6 +629,7 @@ export async function updateExchangeFromUrlHandler( protocolVersion: keysInfo.protocolVersion, signingKeys: keysInfo.signingKeys, reserveClosingDelay: keysInfo.reserveClosingDelay, + globalFees, exchangeBaseUrl: r.baseUrl, wireInfo, termsOfServiceText: tosDownload.tosText, -- cgit v1.2.3