exchange

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

commit cbc42d229bdd1b33fee821d0877daf74c098f9c4
parent 10d8da9c34cf1a41d9a4cfa4b040eed945b9e944
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 23 Jun 2024 11:26:22 +0200

handle POST aml /decision

Diffstat:
Msrc/exchange/taler-exchange-httpd_aml-decision.c | 340++++++++++++++++++++-----------------------------------------------------------
Msrc/include/taler_crypto_lib.h | 8++++++++
Msrc/include/taler_kyclogic_lib.h | 11+++++++++++
Msrc/kyclogic/kyclogic_api.c | 24++++++++++++++++--------
Msrc/util/aml_signatures.c | 35+++++++++++++++++++++++++++--------
5 files changed, 148 insertions(+), 270 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c @@ -15,7 +15,7 @@ */ /** * @file taler-exchange-httpd_aml-decision.c - * @brief Handle request about an AML decision. + * @brief Handle POST request about an AML decision. * @author Christian Grothoff */ #include "platform.h" @@ -31,205 +31,6 @@ #include "taler-exchange-httpd_responses.h" -/** - * Closure for #make_aml_decision() - */ -struct DecisionContext -{ - /** - * Justification given for the decision. - */ - const char *justification; - - /** - * When was the decision taken. - */ - struct GNUNET_TIME_Timestamp decision_time; - - /** - * New rules after the decision. - */ - const json_t *new_rules; - - /** - * Hash of payto://-URI of affected account. - */ - struct TALER_PaytoHashP h_payto; - - /** - * Signature affirming the decision. - */ - struct TALER_AmlOfficerSignatureP officer_sig; - - /** - * Public key of the AML officer. - */ - const struct TALER_AmlOfficerPublicKeyP *officer_pub; - -}; - - -/** - * Function implementing AML decision database transaction. - * - * Runs the transaction logic; IF it returns a non-error code, the - * transaction logic MUST NOT queue a MHD response. IF it returns an hard - * error, the transaction logic MUST queue a MHD response and set @a mhd_ret. - * IF it returns the soft error code, the function MAY be called again to - * retry and MUST not queue a MHD response. - * - * @param cls closure with a `struct DecisionContext` - * @param connection MHD request which triggered the transaction - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -make_aml_decision (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) -{ - struct DecisionContext *dc = cls; - struct GNUNET_TIME_Timestamp last_date; - bool invalid_officer = -1; - -#if FIXME - enum GNUNET_DB_QueryStatus qs; - uint64_t requirement_row = 0; - - if ( (NULL != dc->kyc_requirements) && - (0 != json_array_size (dc->kyc_requirements)) ) - { - char *res = NULL; - size_t idx; - json_t *req; - bool satisfied; - - json_array_foreach (dc->kyc_requirements, idx, req) - { - const char *r = json_string_value (req); - - if (NULL == res) - { - res = GNUNET_strdup (r); - } - else - { - char *tmp; - - GNUNET_asprintf (&tmp, - "%s %s", - res, - r); - GNUNET_free (res); - res = tmp; - } - } - - { - json_t *kyc_details = NULL; - - qs = TALER_KYCLOGIC_check_satisfied ( - &res, - &dc->h_payto, - &kyc_details, - TEH_plugin->select_satisfied_kyc_processes, - TEH_plugin->cls, - &satisfied); - json_decref (kyc_details); - } - if (qs < 0) - { - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_satisfied_kyc_processes") - ; - return GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; - } - if (! satisfied) - { - qs = TEH_plugin->insert_kyc_requirement_for_account ( - TEH_plugin->cls, - res, - &dc->h_payto, - NULL, /* not a reserve */ - &requirement_row); - if (qs < 0) - { - if (GNUNET_DB_STATUS_SOFT_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 GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; - } - } - GNUNET_free (res); - } - - qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls, - &dc->h_payto, - &dc->new_threshold, - dc->new_state, - dc->decision_time, - dc->justification, - dc->kyc_requirements, - requirement_row, - dc->officer_pub, - &dc->officer_sig, - &invalid_officer, - &last_date); - if (qs <= 0) - { - if (GNUNET_DB_STATUS_SOFT_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_aml_decision"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; - } -#endif - if (invalid_officer) - { - GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (GNUNET_TIME_timestamp_cmp (last_date, - >=, - dc->decision_time)) - { - GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - MHD_RESULT TEH_handler_post_aml_decision ( struct TEH_RequestContext *rc, @@ -237,22 +38,33 @@ TEH_handler_post_aml_decision ( const json_t *root) { struct MHD_Connection *connection = rc->connection; - struct DecisionContext dc = { - .officer_pub = officer_pub - }; + const char *justification; + bool to_investigate; + struct GNUNET_TIME_Timestamp decision_time; + const json_t *new_rules; + const json_t *properties = NULL; + struct TALER_PaytoHashP h_payto; + struct TALER_AmlOfficerSignatureP officer_sig; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("officer_sig", - &dc.officer_sig), + GNUNET_JSON_spec_string ("justification", + &justification), GNUNET_JSON_spec_fixed_auto ("h_payto", - &dc.h_payto), + &h_payto), GNUNET_JSON_spec_object_const ("new_rules", - &dc.new_rules), - GNUNET_JSON_spec_string ("justification", - &dc.justification), + &new_rules), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("properties", + &properties), + NULL), + GNUNET_JSON_spec_bool ("keep_investigating", + &to_investigate), + GNUNET_JSON_spec_fixed_auto ("officer_sig", + &officer_sig), GNUNET_JSON_spec_timestamp ("decision_time", - &dc.decision_time), + &decision_time), GNUNET_JSON_spec_end () }; + struct GNUNET_TIME_Timestamp expiration_time; { enum GNUNET_GenericReturnValue res; @@ -271,12 +83,14 @@ TEH_handler_post_aml_decision ( TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != TALER_officer_aml_decision_verify ( - dc.justification, - dc.decision_time, - &dc.h_payto, - dc.new_rules, - dc.officer_pub, - &dc.officer_sig)) + justification, + decision_time, + &h_payto, + new_rules, + properties, + to_investigate, + officer_pub, + &officer_sig)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( @@ -286,52 +100,70 @@ TEH_handler_post_aml_decision ( NULL); } -#if 0 - if (NULL != dc.kyc_requirements) { - size_t index; - json_t *elem; + struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; - json_array_foreach (dc.kyc_requirements, index, elem) + lrs = TALER_KYCLOGIC_rules_parse (new_rules); + if (NULL == lrs) { - const char *val; - - if (! json_is_string (elem)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "kyc_requirements array members must be strings"); - } - val = json_string_value (elem); - if (GNUNET_SYSERR == - TALER_KYCLOGIC_check_satisfiable (val)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_AML_DECISION_UNKNOWN_CHECK, - val); - } + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "legitimization rule malformed"); } + expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs); + + TALER_KYCLOGIC_rules_free (lrs); } -#endif { - MHD_RESULT mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "make-aml-decision", - TEH_MT_REQUEST_OTHER, - &mhd_ret, - &make_aml_decision, - &dc)) + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp last_date; + bool invalid_officer = true; + + qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls, + &h_payto, + decision_time, + expiration_time, + properties, + new_rules, + to_investigate, + justification, + officer_pub, + &officer_sig, + &invalid_officer, + &last_date); + if (qs <= 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_aml_decision"); + } + if (invalid_officer) { - return mhd_ret; + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (GNUNET_TIME_timestamp_cmp (last_date, + >=, + decision_time)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT, + NULL); } } return TALER_MHD_reply_static ( diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h @@ -2667,6 +2667,8 @@ TALER_officer_aml_query_verify ( * decision is about * @param new_rules new KYC rules to apply to the account * Must be a "LegitimizationRuleSet". + * @param properties properties of the account, can be NULL + * @param to_investigate true if the account should be investigated by AML staff * @param officer_priv private key of AML officer * @param[out] officer_sig where to write the signature */ @@ -2676,6 +2678,8 @@ TALER_officer_aml_decision_sign ( struct GNUNET_TIME_Timestamp decision_time, const struct TALER_PaytoHashP *h_payto, const json_t *new_rules, + const json_t *properties, + bool to_investigate, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, struct TALER_AmlOfficerSignatureP *officer_sig); @@ -2688,6 +2692,8 @@ TALER_officer_aml_decision_sign ( * @param h_payto payto URI hash of the account the * decision is about * @param new_rules new KYC rules to apply to the account + * @param properties properties of the account, can be NULL + * @param to_investigate true if the account should be investigated by AML staff * @param officer_pub public key of AML officer * @param officer_sig signature to verify * @return #GNUNET_OK if the signature is valid @@ -2698,6 +2704,8 @@ TALER_officer_aml_decision_verify ( struct GNUNET_TIME_Timestamp decision_time, const struct TALER_PaytoHashP *h_payto, const json_t *new_rules, + const json_t *properties, + bool to_investigate, const struct TALER_AmlOfficerPublicKeyP *officer_pub, const struct TALER_AmlOfficerSignatureP *officer_sig); diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h @@ -516,6 +516,17 @@ TALER_KYCLOGIC_lookup_logic ( /** + * Return expiration time for the given @a lrs + * + * @param lrs legitimization rules to inspect + * @return expiration time + */ +struct GNUNET_TIME_Timestamp +TALER_KYCLOGIC_rules_get_expiration ( + const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs); + + +/** * Function called with the provider details and * associated plugin closures for matching logics. * diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c @@ -301,6 +301,14 @@ static struct TALER_KYCLOGIC_AmlProgram **aml_programs; static unsigned int num_aml_programs; +struct GNUNET_TIME_Timestamp +TALER_KYCLOGIC_rules_get_expiration ( + const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs) +{ + return lrs->expiration_time; +} + + /** * Lookup a KYC check by @a check_name * @@ -334,20 +342,20 @@ TALER_KYCLOGIC_rules_parse (const json_t *jlrs) const json_t *jrules; const json_t *jcustom_measures = NULL; struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ( + "expiration_time", + &expiration_time), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("custom_measures", - &jcustom_measures), + GNUNET_JSON_spec_string ( + "successor_measure", + &successor_measure), NULL), GNUNET_JSON_spec_array_const ("rules", &jrules), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ( - "successor_measure", - &successor_measure), + GNUNET_JSON_spec_object_const ("custom_measures", + &jcustom_measures), NULL), - GNUNET_JSON_spec_timestamp ( - "expiration_time", - &expiration_time), GNUNET_JSON_spec_end () }; struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; diff --git a/src/util/aml_signatures.c b/src/util/aml_signatures.c @@ -52,10 +52,19 @@ struct TALER_AmlDecisionPS struct GNUNET_HashCode h_justification GNUNET_PACKED; /** + * Hash over the justification text. + */ + struct GNUNET_HashCode h_properties GNUNET_PACKED; + + /** * Hash over JSON object with new KYC rules. */ struct GNUNET_HashCode h_new_rules; + /** + * 0: no investigation, 1: yes investigation. + */ + uint64_t flags; }; GNUNET_NETWORK_STRUCT_END @@ -66,6 +75,8 @@ TALER_officer_aml_decision_sign ( struct GNUNET_TIME_Timestamp decision_time, const struct TALER_PaytoHashP *h_payto, const json_t *new_rules, + const json_t *properties, + bool to_investigate, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, struct TALER_AmlOfficerSignatureP *officer_sig) { @@ -73,15 +84,18 @@ TALER_officer_aml_decision_sign ( .purpose.purpose = htonl (TALER_SIGNATURE_AML_DECISION), .purpose.size = htonl (sizeof (ad)), .decision_time = GNUNET_TIME_timestamp_hton (decision_time), - .h_payto = *h_payto + .h_payto = *h_payto, + .flags = GNUNET_htonll (to_investigate ? 1 : 0) }; GNUNET_CRYPTO_hash (justification, strlen (justification), &ad.h_justification); - if (NULL != new_rules) - TALER_json_hash (new_rules, - &ad.h_new_rules); + if (NULL != properties) + TALER_json_hash (properties, + &ad.h_properties); + TALER_json_hash (new_rules, + &ad.h_new_rules); GNUNET_CRYPTO_eddsa_sign (&officer_priv->eddsa_priv, &ad, &officer_sig->eddsa_signature); @@ -94,6 +108,8 @@ TALER_officer_aml_decision_verify ( struct GNUNET_TIME_Timestamp decision_time, const struct TALER_PaytoHashP *h_payto, const json_t *new_rules, + const json_t *properties, + bool to_investigate, const struct TALER_AmlOfficerPublicKeyP *officer_pub, const struct TALER_AmlOfficerSignatureP *officer_sig) { @@ -101,15 +117,18 @@ TALER_officer_aml_decision_verify ( .purpose.purpose = htonl (TALER_SIGNATURE_AML_DECISION), .purpose.size = htonl (sizeof (ad)), .decision_time = GNUNET_TIME_timestamp_hton (decision_time), - .h_payto = *h_payto + .h_payto = *h_payto, + .flags = GNUNET_htonll (to_investigate ? 1 : 0) }; GNUNET_CRYPTO_hash (justification, strlen (justification), &ad.h_justification); - if (NULL != new_rules) - TALER_json_hash (new_rules, - &ad.h_new_rules); + if (NULL != properties) + TALER_json_hash (properties, + &ad.h_properties); + TALER_json_hash (new_rules, + &ad.h_new_rules); return GNUNET_CRYPTO_eddsa_verify ( TALER_SIGNATURE_AML_DECISION, &ad,