diff options
Diffstat (limited to 'packages')
5 files changed, 75 insertions, 1 deletions
diff --git a/packages/taler-wallet-core/src/TalerErrorCode.ts b/packages/taler-wallet-core/src/TalerErrorCode.ts index bb97574ba..239dc2bd4 100644 --- a/packages/taler-wallet-core/src/TalerErrorCode.ts +++ b/packages/taler-wallet-core/src/TalerErrorCode.ts @@ -3308,6 +3308,13 @@ export enum TalerErrorCode { WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH = 7018, /** + * The merchant's signature on the contract terms is invalid. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_CONTRACT_TERMS_SIGNATURE_INVALID = 7019, + + /** * my comment * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). * (A value of 0 indicates that the error is generated client-side). diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 44de6b674..286de5a17 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -398,6 +398,20 @@ export class CryptoApi { ); } + isValidContractTermsSignature( + contractTermsHash: string, + sig: string, + merchantPub: string, + ): Promise<boolean> { + return this.doRpc<boolean>( + "isValidContractTermsSignature", + 4, + contractTermsHash, + sig, + merchantPub, + ); + } + createRecoupRequest(coin: CoinRecord): Promise<RecoupRequest> { return this.doRpc<RecoupRequest>("createRecoupRequest", 1, coin); } diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index c39ebb44a..46ac7c8a6 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -80,6 +80,7 @@ enum SignaturePurpose { WALLET_COIN_MELT = 1202, TEST = 4242, MERCHANT_PAYMENT_OK = 1104, + MERCHANT_CONTRACT = 1101, WALLET_COIN_RECOUP = 1203, WALLET_COIN_LINK = 1204, EXCHANGE_CONFIRM_RECOUP = 1039, @@ -297,6 +298,18 @@ export class CryptoImplementation { return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); } + isValidContractTermsSignature( + contractTermsHash: string, + sig: string, + merchantPub: string, + ): boolean { + const cthDec = decodeCrock(contractTermsHash); + const p = buildSigPS(SignaturePurpose.MERCHANT_CONTRACT) + .put(cthDec) + .build(); + return eddsaVerify(p, decodeCrock(sig), decodeCrock(merchantPub)); + } + /** * Create a new EdDSA key pair. */ diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 0c45290fc..6079ea08f 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -58,6 +58,7 @@ import { Logger } from "../util/logging"; import { parsePayUri } from "../util/taleruri"; import { guardOperationException, + makeErrorDetails, OperationFailedAndReportedError, OperationFailedError, } from "./errors"; @@ -582,6 +583,19 @@ async function resetDownloadProposalRetry( }); } +async function failProposalPermanently( + ws: InternalWalletState, + proposalId: string, + err: TalerErrorDetails, +): Promise<void> { + await ws.db.mutate(Stores.proposals, proposalId, (x) => { + x.retryInfo.active = false; + x.lastError = err; + x.proposalStatus = ProposalStatus.PERMANENTLY_FAILED; + return x; + }); +} + function getProposalRequestTimeout(proposal: ProposalRecord): Duration { return durationMax( { d_ms: 60000 }, @@ -663,13 +677,33 @@ async function processDownloadProposalImpl( const parsedContractTerms = codecForContractTerms().decode( proposalResp.contract_terms, ); + + const sigValid = await ws.cryptoApi.isValidContractTermsSignature( + contractTermsHash, + proposalResp.sig, + parsedContractTerms.merchant_pub, + ); + + if (!sigValid) { + const err = makeErrorDetails( + TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID, + "merchant's signature on contract terms is invalid", + { + merchantPub: parsedContractTerms.merchant_pub, + orderId: parsedContractTerms.order_id, + }, + ); + await failProposalPermanently(ws, proposalId, err); + throw new OperationFailedAndReportedError(err); + } + const fulfillmentUrl = parsedContractTerms.fulfillment_url; const baseUrlForDownload = proposal.merchantBaseUrl; const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url; if (baseUrlForDownload !== baseUrlFromContractTerms) { - throw OperationFailedAndReportedError.fromCode( + const err = makeErrorDetails( TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH, "merchant base URL mismatch", { @@ -677,6 +711,8 @@ async function processDownloadProposalImpl( baseUrlFromContractTerms, }, ); + await failProposalPermanently(ws, proposalId, err); + throw new OperationFailedAndReportedError(err); } await ws.db.runWithWriteTransaction( diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts index 4cd98cac1..8f6c22f22 100644 --- a/packages/taler-wallet-core/src/types/dbTypes.ts +++ b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -814,6 +814,10 @@ export enum ProposalStatus { */ REFUSED = "refused", /** + * Downloading or processing the proposal has failed permanently. + */ + PERMANENTLY_FAILED = "permanently-failed", + /** * Downloaded proposal was detected as a re-purchase. */ REPURCHASE = "repurchase", |