exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit a8d37edac35b6002d0fe5d366ce2f4fb0feaeeef
parent a5c5f597d87fed28cfc49369b3bf7e98f6eacf8c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  4 Dec 2024 22:24:52 +0100

more KYC logic refactoring

Diffstat:
Msrc/exchange/taler-exchange-httpd_common_kyc.c | 421++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/exchangedb/exchangedb_aml.c | 9+++++++++
Msrc/include/taler_crypto_lib.h | 2++
Msrc/kyclogic/kyclogic_api.c | 3++-
4 files changed, 235 insertions(+), 200 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c @@ -849,6 +849,12 @@ struct TEH_LegitimizationCheckHandle struct TEH_KycMeasureRunContext *kat; /** + * Handle for the task that gets us the latest + * applicable rules. + */ + struct TALER_EXCHANGEDB_RuleUpdater *ru; + + /** * Payto-URI of the account. */ struct TALER_FullPayto payto_uri; @@ -922,6 +928,15 @@ struct TEH_LegitimizationCheckHandle */ bool have_merchant_pub; + /** + * Set to true if the merchant public key does not + * match the public key we have on file for this + * target account *and* a rule actually triggered + * for this operation (and thus a new KYC AUTH is + * required). + */ + bool bad_kyc_auth; + }; @@ -1208,6 +1223,7 @@ amount_iterator_wrapper_cb ( so we indeed have a problem! */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC: Mismatch between merchant_pub and target_pub is relevant!\n"); + lch->bad_kyc_auth = true; } return lch->ai (lch->ai_cls, limit, @@ -1216,212 +1232,36 @@ amount_iterator_wrapper_cb ( } +/** + * Function called with the current rule set. + * + * @param cls a `struct TEH_LegitimizationCheckHandle *` + * @param legitimization_outcome_last_row row the rule set is based on + * @param rur includes legitimziation rule set that applies to the account + * (owned by callee, callee must free the lrs!) + */ static void -legitimization_check_run ( - struct TEH_LegitimizationCheckHandle *lch) +current_rules_cb ( + void *cls, + struct TALER_EXCHANGEDB_RuleUpdaterResult *rur) { - struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL; - const struct TALER_KYCLOGIC_KycRule *requirement; + struct TEH_LegitimizationCheckHandle *lch = cls; + struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = rur->lrs; + struct GNUNET_AsyncScopeSave old_scope; enum GNUNET_DB_QueryStatus qs; + const struct TALER_KYCLOGIC_KycRule *requirement; const struct TALER_KYCLOGIC_Measure *instant_ms; - struct GNUNET_AsyncScopeSave old_scope; - if (! TEH_enable_kyc) - { - /* AML/KYC disabled, just immediately return success! */ - lch->lcr.kyc.requirement_row = 0; - lch->lcr.kyc.ok = true; - lch->lcr.bad_kyc_auth = false; - lch->lcr.expiration_date - = GNUNET_TIME_UNIT_FOREVER_TS; - memset (&lch->lcr.next_threshold, - 0, - sizeof (struct TALER_Amount)); - lch->lcr.http_status = 0; - lch->lcr.response = NULL; - lch->async_task - = GNUNET_SCHEDULER_add_now ( - &async_return_legi_result, - lch); - return; - } GNUNET_async_scope_enter (&lch->scope, &old_scope); + if (TALER_EC_NONE != rur->ec) { - json_t *jrules; - bool no_account_pub; - bool no_reserve_pub; - - qs = TEH_plugin->get_kyc_rules ( - TEH_plugin->cls, - &lch->h_payto, - &no_account_pub, - &lch->lcr.kyc.account_pub, - &no_reserve_pub, - &lch->lcr.reserve_pub.reserve_pub, - &jrules); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - legi_fail (lch, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_kyc_rules"); - GNUNET_async_scope_restore (&old_scope); - return; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "get_kyc_rules returned %d/%d/%d/%d\n", - (int) qs, - ! no_account_pub, - ! no_reserve_pub, - NULL != jrules); - - lch->lcr.kyc.have_account_pub - = ! no_account_pub; - lch->lcr.have_reserve_pub - = ! no_reserve_pub; - if ( (lch->have_merchant_pub) && - ( (! lch->lcr.kyc.have_account_pub) || - (0 != - GNUNET_memcmp (&lch->merchant_pub, - &lch->lcr.kyc.account_pub.merchant_pub)) ) && - ( (! lch->lcr.have_reserve_pub) || - (0 != - GNUNET_memcmp (&lch->merchant_pub, - &lch->lcr.reserve_pub.merchant_pub)) ) ) - { - if (NULL == jrules) - { - /* We do not have custom rules, defer enforcing merchant_pub - match until we actually have deposit constraints */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC: merchant_pub given but no known target_pub(%d)/reserve_pub(%d) match (%d)!\n", - lch->lcr.kyc.have_account_pub, - lch->lcr.have_reserve_pub, - (int) qs); - lch->lcr.bad_kyc_auth = true; - } - else - { - /* We have custom rules, but the target_pub for - those custom rules does not match the - merchant_pub. Fail the KYC process! */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC: merchant_pub does not match target_pub of custom rules!\n"); - json_decref (jrules); - fail_kyc_auth (lch); - goto cleanup; - } - } - - /* parse and free jrules (if we had any) */ - if (NULL != jrules) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC: have custom KYC rules for this account!\n"); - lrs = TALER_KYCLOGIC_rules_parse (jrules); - GNUNET_break (NULL != lrs); - /* Fall back to default rules on parse error! */ - json_decref (jrules); - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC: default KYC rules apply to this account!\n"); - } - } - - /* FIXME(fdold, 2024-11-08): We are doing the same logic - here and in kyc-info, abstract it out? */ - /* FIXME(cg-2024-12-02): Also some duplication with - code around run_measure in taler-exchange-aggregator! */ - - /* Check if ruleset is expired and we need to run the successor measure */ - if (NULL != lrs) - { - struct GNUNET_TIME_Timestamp ts; - - ts = TALER_KYCLOGIC_rules_get_expiration (lrs); - if (GNUNET_TIME_absolute_is_past (ts.abs_time)) - { - const struct TALER_KYCLOGIC_Measure *successor_measure; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Current KYC ruleset expired, running successor measure.\n"); - - successor_measure = TALER_KYCLOGIC_rules_get_successor (lrs); - if (NULL == successor_measure) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Successor measure unknown, falling back to default rules!\n"); - TALER_KYCLOGIC_rules_free (lrs); - lrs = NULL; - } - else if (0 == strcmp (successor_measure->prog_name, "SKIP")) - { - lch->measure_run_ctx = TEH_kyc_run_measure_directly ( - &lch->scope, - successor_measure, - &lch->h_payto, - &legi_check_aml_trigger_cb, - lch); - if (NULL == lch->measure_run_ctx) - { - legi_fail (lch, - TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE, - "successor measure"); - } - goto cleanup; - } - else - { - bool unknown_account; - struct GNUNET_TIME_Timestamp decision_time - = GNUNET_TIME_timestamp_get (); - struct GNUNET_TIME_Timestamp last_date; - json_t *succ_jmeasures = TALER_KYCLOGIC_get_jmeasures ( - lrs, - successor_measure->measure_name); - - GNUNET_assert (NULL != succ_jmeasures); - qs = TEH_plugin->insert_successor_measure ( - TEH_plugin->cls, - &lch->h_payto, - decision_time, - successor_measure->measure_name, - succ_jmeasures, - &unknown_account, - &last_date); - json_decref (succ_jmeasures); - if (qs <= 0) - { - legi_fail (lch, - TALER_EC_GENERIC_DB_STORE_FAILED, - "insert_successor_measure"); - goto cleanup; - } - if (unknown_account) - { - legi_fail (lch, - TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN, - NULL); - goto cleanup; - } - /* We tolerate conflicting decision times for automatic decisions. */ - GNUNET_break ( - GNUNET_TIME_timestamp_cmp (last_date, - >=, - decision_time)); - /* Back to default rules. */ - TALER_KYCLOGIC_rules_free (lrs); - lrs = NULL; - } - } + /* rollback should not be needed, but better be safe */ + TEH_plugin->rollback (TEH_plugin->cls); + legi_fail (lch, + rur->ec, + rur->hint); + goto cleanup; } qs = TALER_KYCLOGIC_kyc_test_required ( @@ -1434,13 +1274,25 @@ legitimization_check_run ( if (qs < 0) { GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); legi_fail (lch, TALER_EC_GENERIC_DB_FETCH_FAILED, "kyc_test_required"); goto cleanup; } - if (lch->lcr.bad_kyc_auth) + // FIXME: check that this change is a valid correction, + // used to be just lch->lcr.bad_key_auth, but that + // would be independent of there being an applicable KYC rule! + if (lch->bad_kyc_auth) { + qs = TEH_plugin->commit (TEH_plugin->cls); + if (0 > qs) + { + legi_fail (lch, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "kyc_test_required"); + goto cleanup; + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC auth required\n"); fail_kyc_auth (lch); @@ -1449,6 +1301,14 @@ legitimization_check_run ( if (NULL == requirement) { + qs = TEH_plugin->commit (TEH_plugin->cls); + if (0 > qs) + { + legi_fail (lch, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "kyc_test_required"); + goto cleanup; + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC check passed\n"); lch->lcr.kyc.ok = true; @@ -1468,6 +1328,8 @@ legitimization_check_run ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC requirement is %s\n", TALER_KYCLOGIC_rule2s (requirement)); + // FIXME: this is again logic that should probably be + // shared! instant_ms = TALER_KYCLOGIC_rule_get_instant_measure ( requirement); @@ -1487,10 +1349,19 @@ legitimization_check_run ( if (NULL == lch->kat) { GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); legi_fail (lch, TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE, NULL); } + qs = TEH_plugin->commit (TEH_plugin->cls); + if (0 > qs) + { + legi_fail (lch, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "kyc_test_required"); + goto cleanup; + } goto cleanup; } @@ -1524,6 +1395,7 @@ legitimization_check_run ( if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); legi_fail (lch, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, "trigger_kyc_rule_for_account"); @@ -1532,11 +1404,20 @@ legitimization_check_run ( if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); legi_fail (lch, TALER_EC_GENERIC_DB_STORE_FAILED, "trigger_kyc_rule_for_account"); goto cleanup; } + qs = TEH_plugin->commit (TEH_plugin->cls); + if (0 > qs) + { + legi_fail (lch, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "kyc_test_required"); + goto cleanup; + } /* return success! */ lch->async_task = GNUNET_SCHEDULER_add_now ( @@ -1548,6 +1429,148 @@ cleanup: } +static void +legitimization_check_run ( + struct TEH_LegitimizationCheckHandle *lch) +{ + struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_AsyncScopeSave old_scope; + + if (! TEH_enable_kyc) + { + /* AML/KYC disabled, just immediately return success! */ + lch->lcr.kyc.requirement_row = 0; + lch->lcr.kyc.ok = true; + lch->lcr.bad_kyc_auth = false; + lch->lcr.expiration_date + = GNUNET_TIME_UNIT_FOREVER_TS; + memset (&lch->lcr.next_threshold, + 0, + sizeof (struct TALER_Amount)); + lch->lcr.http_status = 0; + lch->lcr.response = NULL; + lch->async_task + = GNUNET_SCHEDULER_add_now ( + &async_return_legi_result, + lch); + return; + } + GNUNET_async_scope_enter (&lch->scope, + &old_scope); + { + json_t *jrules; + bool no_account_pub; + bool no_reserve_pub; + + qs = TEH_plugin->get_kyc_rules ( + TEH_plugin->cls, + &lch->h_payto, + &no_account_pub, + &lch->lcr.kyc.account_pub, + &no_reserve_pub, + &lch->lcr.reserve_pub.reserve_pub, + &jrules); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + legi_fail (lch, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_kyc_rules"); + GNUNET_async_scope_restore (&old_scope); + return; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "get_kyc_rules returned %d/%d/%d/%d\n", + (int) qs, + ! no_account_pub, + ! no_reserve_pub, + NULL != jrules); + + lch->lcr.kyc.have_account_pub + = ! no_account_pub; + lch->lcr.have_reserve_pub + = ! no_reserve_pub; + if ( (lch->have_merchant_pub) && + ( (! lch->lcr.kyc.have_account_pub) || + (0 != + GNUNET_memcmp (&lch->merchant_pub, + &lch->lcr.kyc.account_pub.merchant_pub)) ) && + ( (! lch->lcr.have_reserve_pub) || + (0 != + GNUNET_memcmp (&lch->merchant_pub, + &lch->lcr.reserve_pub.merchant_pub)) ) ) + { + if (NULL == jrules) + { + /* We do not have custom rules, defer enforcing merchant_pub + match until we actually have deposit constraints */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC: merchant_pub given but no known target_pub(%d)/reserve_pub(%d) match (%d)!\n", + lch->lcr.kyc.have_account_pub, + lch->lcr.have_reserve_pub, + (int) qs); + lch->lcr.bad_kyc_auth = true; + } + else + { + /* We have custom rules, but the target_pub for + those custom rules does not match the + merchant_pub. Fail the KYC process! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC: merchant_pub does not match target_pub of custom rules!\n"); + json_decref (jrules); + fail_kyc_auth (lch); + goto cleanup; + } + } + + /* parse and free jrules (if we had any) */ + if (NULL != jrules) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC: have custom KYC rules for this account!\n"); + lrs = TALER_KYCLOGIC_rules_parse (jrules); + GNUNET_break (NULL != lrs); + /* Fall back to default rules on parse error! */ + json_decref (jrules); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC: default KYC rules apply to this account!\n"); + } + } + + if (NULL != lrs) + { + lch->ru = TALER_EXCHANGEDB_update_rules (TEH_plugin, + &TEH_attribute_key, + &lch->h_payto, + &current_rules_cb, + lch); + } + else + { + struct TALER_EXCHANGEDB_RuleUpdaterResult rur = { 0 }; + + current_rules_cb (lch, + &rur); + } + /* FIXME(fdold, 2024-11-08): We are doing the same logic + here and in kyc-info, abstract it out? */ + /* FIXME(cg-2024-12-02): Also some duplication with + code around run_measure in taler-exchange-aggregator! */ +cleanup: + GNUNET_async_scope_restore (&old_scope); +} + + void TEH_legitimization_check_cancel ( struct TEH_LegitimizationCheckHandle *lch) diff --git a/src/exchangedb/exchangedb_aml.c b/src/exchangedb/exchangedb_aml.c @@ -453,6 +453,13 @@ run_measure (struct TALER_EXCHANGEDB_RuleUpdater *ru, default: break; } + if (unknown_account) + { + fail_update (ru, + TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN, + NULL); + return; + } } /* The rules remain these rules until the user passes the check */ finish_update (ru); @@ -485,6 +492,8 @@ update_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) static void check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) { + const struct TALER_KYCLOGIC_Measure *m; + ru->depth++; if (ru->depth > MAX_DEPTH) { diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h @@ -6258,6 +6258,8 @@ TALER_age_commitment_proof_duplicate ( * @param[in] acp The original age commitment proof * @param[out] nacp The struct to copy the data into, with freshly allocated and copied keys. */ +/* FIXME: API flaw: arguments of this _copy are swapped with + the argument order for the other _copy() APIs... */ void TALER_age_commitment_proof_deep_copy ( const struct TALER_AgeCommitmentProof *acp, diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c @@ -1015,7 +1015,8 @@ TALER_KYCLOGIC_rule_get_instant_measure ( const char *measure_name = r->next_measures[i]; const struct TALER_KYCLOGIC_Measure *ms; - if (0 == strcasecmp (measure_name, KYC_MEASURE_IMPOSSIBLE)) + if (0 == strcasecmp (measure_name, + KYC_MEASURE_IMPOSSIBLE)) { /* If any of the measures if verboten, we do not even consider execution of the instant measure. */