diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_reserves_purse.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_reserves_purse.c | 417 |
1 files changed, 248 insertions, 169 deletions
diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.c b/src/exchange/taler-exchange-httpd_reserves_purse.c index b4035b55c..5e06db206 100644 --- a/src/exchange/taler-exchange-httpd_reserves_purse.c +++ b/src/exchange/taler-exchange-httpd_reserves_purse.c @@ -27,6 +27,7 @@ #include <microhttpd.h> #include <pthread.h> #include "taler_json_lib.h" +#include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_reserves_purse.h" #include "taler-exchange-httpd_responses.h" @@ -56,11 +57,6 @@ struct ReservePurseContext struct TALER_ReserveSignatureP reserve_sig; /** - * Total amount to be put into the purse. - */ - struct TALER_Amount amount; - - /** * Purse fee the client is willing to pay. */ struct TALER_Amount purse_fee; @@ -71,11 +67,6 @@ struct ReservePurseContext struct TALER_Amount deposit_total; /** - * When should the purse expire. - */ - struct GNUNET_TIME_Timestamp purse_expiration; - - /** * Merge time. */ struct GNUNET_TIME_Timestamp merge_timestamp; @@ -86,6 +77,11 @@ struct ReservePurseContext struct GNUNET_TIME_Timestamp exchange_timestamp; /** + * Details about an encrypted contract, if any. + */ + struct TALER_EncryptedContract econtract; + + /** * Merge key for the purse. */ struct TALER_PurseMergePublicKeyP merge_pub; @@ -96,39 +92,24 @@ struct ReservePurseContext struct TALER_PurseMergeSignatureP merge_sig; /** - * Contract decryption key for the purse. - */ - struct TALER_ContractDiffiePublicP contract_pub; - - /** - * Public key of the purse we are creating. - */ - struct TALER_PurseContractPublicKeyP purse_pub; - - /** * Signature of the client affiming this request. */ struct TALER_PurseContractSignatureP purse_sig; /** - * Signature of the client affiming this encrypted contract. + * Fundamental details about the purse. */ - struct TALER_PurseContractSignatureP econtract_sig; + struct TEH_PurseDetails pd; /** - * Hash of the contract terms of the purse. + * Hash of the @e payto_uri. */ - struct TALER_PrivateContractHashP h_contract_terms; + struct TALER_PaytoHashP h_payto; /** - * Encrypted contract, can be NULL. + * KYC status of the operation. */ - void *econtract; - - /** - * Number of bytes in @e econtract. - */ - size_t econtract_size; + struct TALER_EXCHANGEDB_KycStatus kyc; /** * Minimum age for deposits into this purse. @@ -139,52 +120,52 @@ struct ReservePurseContext * Flags for the operation. */ enum TALER_WalletAccountMergeFlags flags; + + /** + * Do we lack an @e econtract? + */ + bool no_econtract; + }; /** - * Send confirmation of purse creation success to client. + * Function called to iterate over KYC-relevant + * transaction amounts for a particular time range. + * Called within a database transaction, so must + * not start a new one. * - * @param connection connection to the client - * @param rpc details about the request that succeeded - * @return MHD result code + * @param cls a `struct ReservePurseContext` + * @param limit maximum time-range for which events + * should be fetched (timestamp in the past) + * @param cb function to call on each event found, + * events must be returned in reverse chronological + * order + * @param cb_cls closure for @a cb */ -static MHD_RESULT -reply_purse_success (struct MHD_Connection *connection, - const struct ReservePurseContext *rpc) +static void +amount_iterator (void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls) { - struct TALER_ExchangePublicKeyP pub; - struct TALER_ExchangeSignatureP sig; - enum TALER_ErrorCode ec; - - if (TALER_EC_NONE != - (ec = TALER_exchange_online_purse_created_sign ( - &TEH_keys_exchange_sign_, - rpc->exchange_timestamp, - rpc->purse_expiration, - &rpc->amount, - &rpc->deposit_total, - &rpc->purse_pub, - &rpc->h_contract_terms, - &pub, - &sig))) - { - GNUNET_break (0); - return TALER_MHD_reply_with_ec (connection, - ec, - NULL); - } - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - TALER_JSON_pack_amount ("total_deposited", - &rpc->deposit_total), - GNUNET_JSON_pack_timestamp ("exchange_timestamp", - rpc->exchange_timestamp), - GNUNET_JSON_pack_data_auto ("exchange_sig", - &sig), - GNUNET_JSON_pack_data_auto ("exchange_pub", - &pub)); + struct ReservePurseContext *rpc = cls; + enum GNUNET_DB_QueryStatus qs; + + cb (cb_cls, + &rpc->deposit_total, + GNUNET_TIME_absolute_get ()); + qs = TEH_plugin->select_merge_amounts_for_kyc_check ( + TEH_plugin->cls, + &rpc->h_payto, + limit, + cb, + cb_cls); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got %d additional transactions for this merge and limit %llu\n", + qs, + (unsigned long long) limit.abs_value_us); + GNUNET_break (qs >= 0); } @@ -208,27 +189,72 @@ purse_transaction (void *cls, { struct ReservePurseContext *rpc = cls; enum GNUNET_DB_QueryStatus qs; + char *required; + + qs = TALER_KYCLOGIC_kyc_test_required ( + TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE, + &rpc->h_payto, + TEH_plugin->select_satisfied_kyc_processes, + TEH_plugin->cls, + &amount_iterator, + rpc, + &required); + if (qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + GNUNET_break (0); + *mhd_ret = + TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "kyc_test_required"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (NULL != required) + { + rpc->kyc.ok = false; + qs = TEH_plugin->insert_kyc_requirement_for_account ( + TEH_plugin->cls, + required, + &rpc->h_payto, + rpc->reserve_pub, + &rpc->kyc.requirement_row); + GNUNET_free (required); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_kyc_requirement_for_account"); + } + return qs; + } + rpc->kyc.ok = true; { bool in_conflict = true; + /* 1) store purse */ - qs = TEH_plugin->insert_purse_request (TEH_plugin->cls, - &rpc->purse_pub, - &rpc->merge_pub, - rpc->purse_expiration, - &rpc->h_contract_terms, - rpc->min_age, - rpc->flags, - &rpc->purse_fee, - &rpc->amount, - &rpc->purse_sig, - &in_conflict); + qs = TEH_plugin->insert_purse_request ( + TEH_plugin->cls, + &rpc->pd.purse_pub, + &rpc->merge_pub, + rpc->pd.purse_expiration, + &rpc->pd.h_contract_terms, + rpc->min_age, + rpc->flags, + &rpc->purse_fee, + &rpc->pd.target_amount, + &rpc->purse_sig, + &in_conflict); if (qs < 0) { if (GNUNET_DB_STATUS_SOFT_ERROR == qs) return qs; - TALER_LOG_WARNING ( - "Failed to store purse purse information in database\n"); + GNUNET_break (0); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, @@ -249,15 +275,16 @@ purse_transaction (void *cls, uint32_t min_age; TEH_plugin->rollback (TEH_plugin->cls); - qs = TEH_plugin->select_purse_request (TEH_plugin->cls, - &rpc->purse_pub, - &merge_pub, - &purse_expiration, - &h_contract_terms, - &min_age, - &target_amount, - &balance, - &purse_sig); + qs = TEH_plugin->get_purse_request ( + TEH_plugin->cls, + &rpc->pd.purse_pub, + &merge_pub, + &purse_expiration, + &h_contract_terms, + &min_age, + &target_amount, + &balance, + &purse_sig); if (qs <= 0) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); @@ -289,16 +316,21 @@ purse_transaction (void *cls, &merge_pub)); return GNUNET_DB_STATUS_HARD_ERROR; } + } /* 2) create purse with reserve (and debit reserve for purse creation!) */ { bool in_conflict = true; bool insufficient_funds = true; + bool no_reserve = true; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Creating purse with flags %d\n", + rpc->flags); qs = TEH_plugin->do_reserve_purse ( TEH_plugin->cls, - &rpc->purse_pub, + &rpc->pd.purse_pub, &rpc->merge_sig, rpc->merge_timestamp, &rpc->reserve_sig, @@ -308,6 +340,7 @@ purse_transaction (void *cls, : &rpc->gf->fees.purse, rpc->reserve_pub, &in_conflict, + &no_reserve, &insufficient_funds); if (qs < 0) { @@ -330,6 +363,7 @@ purse_transaction (void *cls, struct GNUNET_TIME_Timestamp merge_timestamp; char *partner_url; struct TALER_ReservePublicKeyP reserve_pub; + bool refunded; TEH_plugin->rollback (TEH_plugin->cls); qs = TEH_plugin->select_purse_merge ( @@ -338,7 +372,8 @@ purse_transaction (void *cls, &merge_sig, &merge_timestamp, &partner_url, - &reserve_pub); + &reserve_pub, + &refunded); if (qs <= 0) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); @@ -351,6 +386,18 @@ purse_transaction (void *cls, "select purse merge"); return GNUNET_DB_STATUS_HARD_ERROR; } + if (refunded) + { + /* This is a bit of a strange case ... */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Purse was already refunded\n"); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_GONE, + TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED, + NULL); + GNUNET_free (partner_url); + return GNUNET_DB_STATUS_HARD_ERROR; + } *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( connection, @@ -370,6 +417,19 @@ purse_transaction (void *cls, GNUNET_free (partner_url); return GNUNET_DB_STATUS_HARD_ERROR; } + if ( (no_reserve) && + ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA + == rpc->flags) || + (! TALER_amount_is_zero (&rpc->gf->fees.purse)) ) ) + { + *mhd_ret + = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN)); + return GNUNET_DB_STATUS_HARD_ERROR; + } if (insufficient_funds) { *mhd_ret @@ -382,16 +442,13 @@ purse_transaction (void *cls, } } /* 3) if present, persist contract */ - if (NULL != rpc->econtract) + if (! rpc->no_econtract) { bool in_conflict = true; qs = TEH_plugin->insert_contract (TEH_plugin->cls, - &rpc->purse_pub, - &rpc->contract_pub, - rpc->econtract_size, - rpc->econtract, - &rpc->econtract_sig, + &rpc->pd.purse_pub, + &rpc->econtract, &in_conflict); if (qs < 0) { @@ -406,18 +463,13 @@ purse_transaction (void *cls, } if (in_conflict) { - struct TALER_ContractDiffiePublicP pub_ckey; - struct TALER_PurseContractSignatureP econtract_sig; - size_t econtract_size; - void *econtract; + struct TALER_EncryptedContract econtract; struct GNUNET_HashCode h_econtract; - qs = TEH_plugin->select_contract_by_purse (TEH_plugin->cls, - &rpc->purse_pub, - &pub_ckey, - &econtract_sig, - &econtract_size, - &econtract); + qs = TEH_plugin->select_contract_by_purse ( + TEH_plugin->cls, + &rpc->pd.purse_pub, + &econtract); if (qs <= 0) { if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -431,8 +483,8 @@ purse_transaction (void *cls, "select contract"); return GNUNET_DB_STATUS_HARD_ERROR; } - GNUNET_CRYPTO_hash (econtract, - econtract_size, + GNUNET_CRYPTO_hash (econtract.econtract, + econtract.econtract_size, &h_econtract); *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( @@ -443,9 +495,10 @@ purse_transaction (void *cls, GNUNET_JSON_pack_data_auto ("h_econtract", &h_econtract), GNUNET_JSON_pack_data_auto ("econtract_sig", - &econtract_sig), - GNUNET_JSON_pack_data_auto ("pub_ckey", - &pub_ckey)); + &econtract.econtract_sig), + GNUNET_JSON_pack_data_auto ("contract_pub", + &econtract.contract_pub)); + GNUNET_free (econtract.econtract); return GNUNET_DB_STATUS_HARD_ERROR; } } @@ -468,7 +521,7 @@ TEH_handler_reserves_purse ( struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("purse_value", TEH_currency, - &rpc.amount), + &rpc.pd.target_amount), GNUNET_JSON_spec_uint32 ("min_age", &rpc.min_age), GNUNET_JSON_spec_mark_optional ( @@ -477,18 +530,9 @@ TEH_handler_reserves_purse ( &rpc.purse_fee), &no_purse_fee), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_varsize ("econtract", - &rpc.econtract, - &rpc.econtract_size), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("econtract_sig", - &rpc.econtract_sig), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("contract_pub", - &rpc.contract_pub), - NULL), + TALER_JSON_spec_econtract ("econtract", + &rpc.econtract), + &rpc.no_econtract), GNUNET_JSON_spec_fixed_auto ("merge_pub", &rpc.merge_pub), GNUNET_JSON_spec_fixed_auto ("merge_sig", @@ -496,15 +540,15 @@ TEH_handler_reserves_purse ( GNUNET_JSON_spec_fixed_auto ("reserve_sig", &rpc.reserve_sig), GNUNET_JSON_spec_fixed_auto ("purse_pub", - &rpc.purse_pub), + &rpc.pd.purse_pub), GNUNET_JSON_spec_fixed_auto ("purse_sig", &rpc.purse_sig), GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &rpc.h_contract_terms), + &rpc.pd.h_contract_terms), GNUNET_JSON_spec_timestamp ("merge_timestamp", &rpc.merge_timestamp), GNUNET_JSON_spec_timestamp ("purse_expiration", - &rpc.purse_expiration), + &rpc.pd.purse_expiration), GNUNET_JSON_spec_end () }; @@ -525,10 +569,39 @@ TEH_handler_reserves_purse ( return MHD_YES; /* failure */ } } + { + char *payto_uri; + + payto_uri = TALER_reserve_make_payto (TEH_base_url, + reserve_pub); + TALER_payto_hash (payto_uri, + &rpc.h_payto); + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_wallet_purse_merge_verify (payto_uri, + rpc.merge_timestamp, + &rpc.pd.purse_pub, + &rpc.merge_pub, + &rpc.merge_sig)) + { + MHD_RESULT ret; + + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID, + payto_uri); + GNUNET_free (payto_uri); + return ret; + } + GNUNET_free (payto_uri); + } GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TEH_currency, &rpc.deposit_total)); - if (GNUNET_TIME_timestamp_cmp (rpc.purse_expiration, + if (GNUNET_TIME_timestamp_cmp (rpc.pd.purse_expiration, <, rpc.exchange_timestamp)) { @@ -539,7 +612,7 @@ TEH_handler_reserves_purse ( TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW, NULL); } - if (GNUNET_TIME_absolute_is_never (rpc.purse_expiration.abs_time)) + if (GNUNET_TIME_absolute_is_never (rpc.pd.purse_expiration.abs_time)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); @@ -548,8 +621,22 @@ TEH_handler_reserves_purse ( TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER, NULL); } - rpc.gf = TEH_keys_global_fee_by_time (TEH_keys_get_state (), - rpc.exchange_timestamp); + { + struct TEH_KeyStateHandle *keys; + + keys = TEH_keys_get_state (); + if (NULL == keys) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + } + rpc.gf = TEH_keys_global_fee_by_time (keys, + rpc.exchange_timestamp); + } if (NULL == rpc.gf) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -563,8 +650,9 @@ TEH_handler_reserves_purse ( if (no_purse_fee) { rpc.flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA; - TALER_amount_set_zero (TEH_currency, - &rpc.purse_fee); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &rpc.purse_fee)); } else { @@ -584,12 +672,12 @@ TEH_handler_reserves_purse ( } TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != - TALER_wallet_purse_create_verify (rpc.purse_expiration, - &rpc.h_contract_terms, + TALER_wallet_purse_create_verify (rpc.pd.purse_expiration, + &rpc.pd.h_contract_terms, &rpc.merge_pub, rpc.min_age, - &rpc.amount, - &rpc.purse_pub, + &rpc.pd.target_amount, + &rpc.pd.purse_pub, &rpc.purse_sig)) { GNUNET_break_op (0); @@ -601,26 +689,11 @@ TEH_handler_reserves_purse ( NULL); } if (GNUNET_OK != - TALER_wallet_purse_merge_verify (TEH_base_url, - rpc.merge_timestamp, - &rpc.purse_pub, - &rpc.merge_pub, - &rpc.merge_sig)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID, - NULL); - } - if (GNUNET_OK != TALER_wallet_account_merge_verify (rpc.merge_timestamp, - &rpc.purse_pub, - rpc.purse_expiration, - &rpc.h_contract_terms, - &rpc.amount, + &rpc.pd.purse_pub, + rpc.pd.purse_expiration, + &rpc.pd.h_contract_terms, + &rpc.pd.target_amount, &rpc.purse_fee, rpc.min_age, rpc.flags, @@ -632,16 +705,16 @@ TEH_handler_reserves_purse ( return TALER_MHD_reply_with_error ( connection, MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID, + TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID, NULL); } - if ( (NULL != rpc.econtract) && + if ( (! rpc.no_econtract) && (GNUNET_OK != - TALER_wallet_econtract_upload_verify (rpc.econtract, - rpc.econtract_size, - &rpc.contract_pub, - &rpc.purse_pub, - &rpc.econtract_sig)) ) + TALER_wallet_econtract_upload_verify (rpc.econtract.econtract, + rpc.econtract.econtract_size, + &rpc.econtract.contract_pub, + &rpc.pd.purse_pub, + &rpc.econtract.econtract_sig)) ) { TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n"); GNUNET_JSON_parse_free (spec); @@ -680,12 +753,18 @@ TEH_handler_reserves_purse ( } } + if (! rpc.kyc.ok) + return TEH_RESPONSE_reply_kyc_required (connection, + &rpc.h_payto, + &rpc.kyc); /* generate regular response */ { MHD_RESULT res; - res = reply_purse_success (connection, - &rpc); + res = TEH_RESPONSE_reply_purse_created (connection, + rpc.exchange_timestamp, + &rpc.deposit_total, + &rpc.pd); GNUNET_JSON_parse_free (spec); return res; } |