check contract terms signature, handle errors
This commit is contained in:
parent
0d37ec5e91
commit
dffb293f2a
@ -3307,6 +3307,13 @@ export enum TalerErrorCode {
|
|||||||
*/
|
*/
|
||||||
WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH = 7018,
|
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
|
* my comment
|
||||||
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
|
* Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500).
|
||||||
|
@ -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> {
|
createRecoupRequest(coin: CoinRecord): Promise<RecoupRequest> {
|
||||||
return this.doRpc<RecoupRequest>("createRecoupRequest", 1, coin);
|
return this.doRpc<RecoupRequest>("createRecoupRequest", 1, coin);
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ enum SignaturePurpose {
|
|||||||
WALLET_COIN_MELT = 1202,
|
WALLET_COIN_MELT = 1202,
|
||||||
TEST = 4242,
|
TEST = 4242,
|
||||||
MERCHANT_PAYMENT_OK = 1104,
|
MERCHANT_PAYMENT_OK = 1104,
|
||||||
|
MERCHANT_CONTRACT = 1101,
|
||||||
WALLET_COIN_RECOUP = 1203,
|
WALLET_COIN_RECOUP = 1203,
|
||||||
WALLET_COIN_LINK = 1204,
|
WALLET_COIN_LINK = 1204,
|
||||||
EXCHANGE_CONFIRM_RECOUP = 1039,
|
EXCHANGE_CONFIRM_RECOUP = 1039,
|
||||||
@ -297,6 +298,18 @@ export class CryptoImplementation {
|
|||||||
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
|
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.
|
* Create a new EdDSA key pair.
|
||||||
*/
|
*/
|
||||||
|
@ -58,6 +58,7 @@ import { Logger } from "../util/logging";
|
|||||||
import { parsePayUri } from "../util/taleruri";
|
import { parsePayUri } from "../util/taleruri";
|
||||||
import {
|
import {
|
||||||
guardOperationException,
|
guardOperationException,
|
||||||
|
makeErrorDetails,
|
||||||
OperationFailedAndReportedError,
|
OperationFailedAndReportedError,
|
||||||
OperationFailedError,
|
OperationFailedError,
|
||||||
} from "./errors";
|
} 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 {
|
function getProposalRequestTimeout(proposal: ProposalRecord): Duration {
|
||||||
return durationMax(
|
return durationMax(
|
||||||
{ d_ms: 60000 },
|
{ d_ms: 60000 },
|
||||||
@ -663,13 +677,33 @@ async function processDownloadProposalImpl(
|
|||||||
const parsedContractTerms = codecForContractTerms().decode(
|
const parsedContractTerms = codecForContractTerms().decode(
|
||||||
proposalResp.contract_terms,
|
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 fulfillmentUrl = parsedContractTerms.fulfillment_url;
|
||||||
|
|
||||||
const baseUrlForDownload = proposal.merchantBaseUrl;
|
const baseUrlForDownload = proposal.merchantBaseUrl;
|
||||||
const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url;
|
const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url;
|
||||||
|
|
||||||
if (baseUrlForDownload !== baseUrlFromContractTerms) {
|
if (baseUrlForDownload !== baseUrlFromContractTerms) {
|
||||||
throw OperationFailedAndReportedError.fromCode(
|
const err = makeErrorDetails(
|
||||||
TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH,
|
TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH,
|
||||||
"merchant base URL mismatch",
|
"merchant base URL mismatch",
|
||||||
{
|
{
|
||||||
@ -677,6 +711,8 @@ async function processDownloadProposalImpl(
|
|||||||
baseUrlFromContractTerms,
|
baseUrlFromContractTerms,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
await failProposalPermanently(ws, proposalId, err);
|
||||||
|
throw new OperationFailedAndReportedError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws.db.runWithWriteTransaction(
|
await ws.db.runWithWriteTransaction(
|
||||||
|
@ -813,6 +813,10 @@ export enum ProposalStatus {
|
|||||||
* The user has rejected the proposal.
|
* The user has rejected the proposal.
|
||||||
*/
|
*/
|
||||||
REFUSED = "refused",
|
REFUSED = "refused",
|
||||||
|
/**
|
||||||
|
* Downloading or processing the proposal has failed permanently.
|
||||||
|
*/
|
||||||
|
PERMANENTLY_FAILED = "permanently-failed",
|
||||||
/**
|
/**
|
||||||
* Downloaded proposal was detected as a re-purchase.
|
* Downloaded proposal was detected as a re-purchase.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user