diff options
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 |
commit | cd0b3436e9a0d89d427e3dda59d28c03b6d4dd2c (patch) | |
tree | 3531f246991a848670278b1853a8758610aadaf0 /src/backend | |
parent | c24d0e772552b2c1dbc043ef759445805793e51e (diff) | |
download | merchant-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.c | 248 |
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 */ |