summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2022-03-15 18:19:08 +0100
committerÖzgür Kesim <oec-taler@kesim.org>2022-03-15 18:19:08 +0100
commitcd0b3436e9a0d89d427e3dda59d28c03b6d4dd2c (patch)
tree3531f246991a848670278b1853a8758610aadaf0 /src/backend
parentc24d0e772552b2c1dbc043ef759445805793e51e (diff)
downloadmerchant-cd0b3436e9a0d89d427e3dda59d28c03b6d4dd2c.tar.gz
merchant-cd0b3436e9a0d89d427e3dda59d28c03b6d4dd2c.tar.bz2
merchant-cd0b3436e9a0d89d427e3dda59d28c03b6d4dd2c.zip
[age restriction] age verification implemented, but not tested.
order/pay now verifies the minimum_age of an order for each coin, using the age restriction API from the exchange-lib. Note, tests are not implemented yet.
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c248
1 files changed, 210 insertions, 38 deletions
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index edd37d24..3bdf5e50 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -1,21 +1,21 @@
/*
- This file is part of TALER
- (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation; either version 3,
- or (at your option) any later version.
-
- 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 TALER; see the file COPYING. If not,
- see <http://www.gnu.org/licenses/>
-*/
+ This file is part of TALER
+ (C) 2014-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ 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 TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+ */
/**
* @file taler-merchant-httpd_post-orders-ID-pay.c
@@ -122,6 +122,25 @@ struct DepositConfirmation
struct TALER_CoinSpendSignatureP coin_sig;
/**
+ * If a minimum age was required (i. e. pc->minimum_age is large enough),
+ * this is the signature of the minimum age (as a single uint8_t), using the
+ * private key to the corresponding age group. Might be NULL.
+ */
+ struct TALER_AgeAttestation *minimum_age_sig;
+
+ /* If a minimum age was required (i. e. pc->minimum_age is large enought),
+ * this is the age commitment (i. e. age mask and vector of EdDSA public
+ * keys, one per age group) that went into the mining of the coin. The
+ * SHA256 hash of the mask and the vector of public keys was bound to the
+ * key.
+ */
+ struct TALER_AgeCommitment *age_commitment;
+
+ /* Age mask in the denomination that defines the age groups. Only
+ * applicable, if minimum age was required. */
+ struct TALER_AgeMask age_mask;
+
+ /**
* Offset of this coin into the `dc` array of all coins in the
* @e pc.
*/
@@ -261,6 +280,12 @@ struct PayContext
*/
struct TALER_Amount max_wire_fee;
+
+ /**
+ * Minimum age required for this purchase.
+ */
+ unsigned int minimum_age;
+
/**
* Amount from @e root. This is the amount the merchant expects
* to make, minus @e max_fee.
@@ -1031,6 +1056,7 @@ process_pay_with_exchange (void *cls,
struct PayContext *pc = cls;
struct TMH_HandlerContext *hc = pc->hc;
const struct TALER_EXCHANGE_Keys *keys;
+ struct TALER_AgeCommitmentHash h_age_commitment = {0};
(void) payto_uri;
pc->fo = NULL;
@@ -1076,10 +1102,11 @@ process_pay_with_exchange (void *cls,
const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
enum TALER_ErrorCode ec;
unsigned int http_status;
+ bool age_verification_required = false;
if (NULL != dc->dh)
continue; /* we were here before (can happen due to
- tried_force_keys logic), don't go again */
+ tried_force_keys logic), don't go again */
if (dc->found_in_db)
continue;
if (0 != strcmp (dc->exchange_url,
@@ -1148,6 +1175,58 @@ process_pay_with_exchange (void *cls,
return;
}
+
+ /* Now that we have the details about the denomination, we can verify age
+ * restriction requirements, if applicable. Note that denominations with an
+ * age_mask equal to zero always pass the age verification. */
+ age_verification_required = 0 < pc->minimum_age &&
+ 0 < denom_details->key.age_mask.bits;
+
+ if (age_verification_required)
+ {
+ unsigned int code = 0;
+
+ if (NULL == dc->age_commitment)
+ {
+ code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING;
+ goto AGE_FAIL;
+ }
+
+ dc->age_commitment->mask = denom_details->key.age_mask;
+ if (dc->age_commitment->num !=
+ __builtin_popcount (dc->age_commitment->mask.bits))
+ {
+ code =
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH;
+ goto AGE_FAIL;
+ }
+
+ if (GNUNET_OK !=
+ TALER_age_commitment_verify (
+ dc->age_commitment,
+ pc->minimum_age,
+ dc->minimum_age_sig))
+ code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED;
+
+AGE_FAIL:
+ if (0 < code)
+ {
+ resume_pay_with_response (
+ pc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (code),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &denom_details->h_key)));
+ return;
+ }
+
+ /* Age restriction successfully verified!
+ * Calculate the hash of the age commitment. */
+ TALER_age_commitment_hash (dc->age_commitment,
+ &h_age_commitment);
+ }
+
dc->deposit_fee = denom_details->fees.deposit;
dc->refund_fee = denom_details->fees.refund;
dc->wire_fee = *wire_fee;
@@ -1159,8 +1238,9 @@ process_pay_with_exchange (void *cls,
pc->wm->payto_uri,
&pc->wm->wire_salt,
&pc->h_contract_terms,
- NULL, /* FIXME-Oec */
- NULL, /* FIXME-Oec */
+ age_verification_required ?
+ &h_age_commitment: NULL,
+ NULL, /* FIXME oec: extension json blob */
&dc->coin_pub,
&dc->ub_sig,
&denom_details->key,
@@ -1932,8 +2012,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MISSING,
"'coins' must be an array"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
pc->coins_cnt = json_array_size (coins);
@@ -1945,8 +2025,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"'coins' array too long"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
/* note: 1 coin = 1 deposit confirmation expected */
@@ -1961,6 +2041,8 @@ parse_pay (struct MHD_Connection *connection,
{
struct DepositConfirmation *dc = &pc->dc[coins_index];
const char *exchange_url;
+ struct TALER_AgeAttestationPS minimum_age_sig = {0};
+ json_t *age_commitment = NULL;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_fixed_auto ("coin_sig",
&dc->coin_sig),
@@ -1975,6 +2057,12 @@ parse_pay (struct MHD_Connection *connection,
&dc->amount_with_fee),
GNUNET_JSON_spec_string ("exchange_url",
&exchange_url),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
+ &minimum_age_sig)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("age_commitment",
+ &age_commitment)),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
@@ -1988,6 +2076,7 @@ parse_pay (struct MHD_Connection *connection,
GNUNET_JSON_parse_free (spec);
return res;
}
+
for (unsigned int j = 0; j<coins_index; j++)
{
if (0 ==
@@ -2000,10 +2089,11 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"duplicate coin in list"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
}
+
dc->exchange_url = GNUNET_strdup (exchange_url);
dc->index = coins_index;
dc->pc = pc;
@@ -2019,6 +2109,74 @@ parse_pay (struct MHD_Connection *connection,
TALER_EC_GENERIC_CURRENCY_MISMATCH,
TMH_currency);
}
+
+ {
+ bool has_commitment = (NULL != age_commitment) &&
+ json_is_array (age_commitment);
+ bool has_sig = ! GNUNET_is_zero_ (&minimum_age_sig,
+ sizeof(minimum_age_sig));
+ if (has_sig != has_commitment)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inconsistency: 'mininum_age_sig' vs. 'age_commitment'");
+ }
+
+ /* Parse the AgeCommitment, i. e. the public keys */
+ if (has_commitment)
+ {
+ json_t *pk;
+ unsigned int idx;
+ size_t num = json_array_size (age_commitment);
+
+ /* Sanity check the amount of AgeCommitment's public keys. The
+ * actual check will be performed once we now the denominations. */
+ if (32 >= num)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'age_commitment' too large");
+ }
+
+ dc->age_commitment = GNUNET_new (struct TALER_AgeCommitment);
+ dc->age_commitment->num = num;
+ dc->age_commitment->keys =
+ GNUNET_new_array (num,
+ struct TALER_AgeCommitmentPublicKeyP);
+ /* Note that dc->age_commitment.mask will be set later, based on
+ * the actual denomination. */
+
+ json_array_foreach (age_commitment, idx, pk) {
+ struct GNUNET_JSON_Specification pkspec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &dc
+ ->age_commitment
+ ->keys[idx].pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (pk,
+ pkspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "age_commitment");
+ }
+ }
+ }
+ }
}
}
GNUNET_JSON_parse_free (spec);
@@ -2047,8 +2205,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"contract terms"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
@@ -2057,8 +2215,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
pc->order_id))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
/* hash contract (needed later) */
@@ -2073,8 +2231,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling payment for order `%s' with contract hash `%s'\n",
@@ -2093,8 +2251,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
/* Get details from contract and check fundamentals */
@@ -2125,10 +2283,15 @@ parse_pay (struct MHD_Connection *connection,
&pc->wire_transfer_deadline),
GNUNET_JSON_spec_fixed_auto ("h_wire",
&pc->h_wire),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("minimum_age",
+ &pc->minimum_age)),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
+ pc->minimum_age = 0;
+
res = TALER_MHD_parse_internal_json_data (connection,
contract_terms,
espec);
@@ -2162,8 +2325,8 @@ parse_pay (struct MHD_Connection *connection,
MHD_HTTP_GONE,
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
}
@@ -2186,6 +2349,15 @@ parse_pay (struct MHD_Connection *connection,
pc->wm = wm;
}
+ if (0 < pc->minimum_age)
+ {
+ /* TODO oec: check
+ * 1. denomination are age restricted
+ * 2. consume their mask
+ * 3. valididate minimum age
+ */
+ }
+
return GNUNET_OK;
}
@@ -2260,8 +2432,8 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
pc);
if (GNUNET_OK != ret)
return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
+ ? MHD_YES
+ : MHD_NO;
}
/* Payment not finished, suspend while we interact with the exchange */