From afe3f70d336e151598e02ebedb6498e13122530e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 14 Feb 2023 14:26:00 +0100 Subject: [PATCH] begin API change to allow AML officers to trigger KYC process --- .../taler-exchange-httpd_aml-decision.c | 14 ++++++++++++++ src/exchangedb/0003-aml_history.sql | 8 +++++++- .../exchange_do_insert_aml_decision.sql | 7 +++++-- src/exchangedb/pg_insert_aml_decision.c | 11 ++++++++++- src/exchangedb/pg_insert_aml_decision.h | 2 ++ src/include/taler_crypto_lib.h | 6 ++++++ src/include/taler_exchange_service.h | 2 ++ src/include/taler_exchangedb_plugin.h | 1 + src/include/taler_testing_lib.h | 2 ++ src/lib/exchange_api_add_aml_decision.c | 5 +++++ src/testing/test_kyc_api.c | 3 +++ .../testing_api_cmd_take_aml_decision.c | 19 +++++++++++++++++++ src/util/aml_signatures.c | 14 ++++++++++++++ 13 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c index 56935df55..0f586279b 100644 --- a/src/exchange/taler-exchange-httpd_aml-decision.c +++ b/src/exchange/taler-exchange-httpd_aml-decision.c @@ -50,6 +50,7 @@ TEH_handler_post_aml_decision ( uint32_t new_state32; enum TALER_AmlDecisionState new_state; struct TALER_AmlOfficerSignatureP officer_sig; + json_t *kyc_requirements = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("officer_sig", &officer_sig), @@ -64,6 +65,10 @@ TEH_handler_post_aml_decision ( &decision_time), GNUNET_JSON_spec_uint32 ("new_state", &new_state32), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("kyc_requirements", + &kyc_requirements), + NULL), GNUNET_JSON_spec_end () }; @@ -89,6 +94,7 @@ TEH_handler_post_aml_decision ( &new_threshold, &h_payto, new_state, + kyc_requirements, officer_pub, &officer_sig)) { @@ -99,6 +105,8 @@ TEH_handler_post_aml_decision ( TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID, NULL); } + + // FIXME: check kyc_requirements is well-formed! { enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Timestamp last_date; @@ -112,6 +120,7 @@ TEH_handler_post_aml_decision ( new_state, decision_time, justification, + kyc_requirements, officer_pub, &officer_sig, &invalid_officer, @@ -127,6 +136,11 @@ TEH_handler_post_aml_decision ( TALER_EC_GENERIC_DB_STORE_FAILED, "add aml_decision"); } + if (NULL != kyc_requirements) + { + // FIXME: act on these! + } + if (invalid_officer) { GNUNET_break_op (0); diff --git a/src/exchangedb/0003-aml_history.sql b/src/exchangedb/0003-aml_history.sql index 36c0b3865..b411a6fb1 100644 --- a/src/exchangedb/0003-aml_history.sql +++ b/src/exchangedb/0003-aml_history.sql @@ -32,6 +32,7 @@ BEGIN ',new_status INT4 NOT NULL DEFAULT(0)' ',decision_time INT8 NOT NULL DEFAULT(0)' ',justification VARCHAR NOT NULL' + ',kyc_requirements VARCHAR' ',decider_pub BYTEA CHECK (LENGTH(decider_pub)=32)' ',decider_sig BYTEA CHECK (LENGTH(decider_sig)=64)' ') %s ;' @@ -80,6 +81,12 @@ BEGIN ,table_name ,partition_suffix ); + PERFORM comment_partitioned_column( + 'Additional KYC requirements imposed by the AML staff member. Serialized JSON array of strings.' + ,'kyc_requirements' + ,table_name + ,partition_suffix + ); PERFORM comment_partitioned_column( 'Signature key of the staff member affirming the AML decision; of type AML_DECISION' ,'decider_sig' @@ -114,7 +121,6 @@ BEGIN ); END $$; --- FIXME: also have INSERT on AML decisions to update AML status! INSERT INTO exchange_tables (name diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql index 0009ecb40..8e22c57f8 100644 --- a/src/exchangedb/exchange_do_insert_aml_decision.sql +++ b/src/exchangedb/exchange_do_insert_aml_decision.sql @@ -24,6 +24,7 @@ CREATE OR REPLACE FUNCTION exchange_do_insert_aml_decision( IN in_decider_pub BYTEA, IN in_decider_sig BYTEA, IN in_notify_s VARCHAR, + IN in_kyc_requirements VARCHAR, OUT out_invalid_officer BOOLEAN, OUT out_last_date INT8) LANGUAGE plpgsql @@ -84,6 +85,7 @@ INSERT INTO exchange.aml_history ,new_status ,decision_time ,justification + ,kyc_requirements ,decider_pub ,decider_sig ) VALUES @@ -93,6 +95,7 @@ INSERT INTO exchange.aml_history ,in_new_status ,in_decision_time ,in_justification + ,in_kyc_requirements ,in_decider_pub ,in_decider_sig); @@ -105,7 +108,7 @@ THEN ,trigger_type) VALUES (in_h_payto,1); - + EXECUTE FORMAT ( 'NOTIFY %s' ,in_notify_s); @@ -116,5 +119,5 @@ END IF; END $$; -COMMENT ON FUNCTION exchange_do_insert_aml_decision(BYTEA, INT8, INT4, INT4, INT8, VARCHAR, BYTEA, BYTEA, VARCHAR) +COMMENT ON FUNCTION exchange_do_insert_aml_decision(BYTEA, INT8, INT4, INT4, INT8, VARCHAR, BYTEA, BYTEA, VARCHAR, VARCHAR) IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table'; diff --git a/src/exchangedb/pg_insert_aml_decision.c b/src/exchangedb/pg_insert_aml_decision.c index 7bdd0f575..5a1b71235 100644 --- a/src/exchangedb/pg_insert_aml_decision.c +++ b/src/exchangedb/pg_insert_aml_decision.c @@ -35,6 +35,7 @@ TEH_PG_insert_aml_decision ( enum TALER_AmlDecisionState new_status, struct GNUNET_TIME_Timestamp decision_time, const char *justification, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPublicKeyP *decider_pub, const struct TALER_AmlOfficerSignatureP *decider_sig, bool *invalid_officer, @@ -48,6 +49,9 @@ TEH_PG_insert_aml_decision ( .h_payto = *h_payto }; char *notify_s = GNUNET_PG_get_event_notify_channel (&rep.header); + char *kyc_s = (NULL != kyc_requirements) + ? json_dumps (kyc_requirements, JSON_COMPACT) + : NULL; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_payto), TALER_PQ_query_param_amount (new_threshold), @@ -57,6 +61,9 @@ TEH_PG_insert_aml_decision ( GNUNET_PQ_query_param_auto_from_type (decider_pub), GNUNET_PQ_query_param_auto_from_type (decider_sig), GNUNET_PQ_query_param_string (notify_s), + NULL != kyc_requirements + ? GNUNET_PQ_query_param_string (kyc_s) + : GNUNET_PQ_query_param_null (), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -74,11 +81,13 @@ TEH_PG_insert_aml_decision ( " out_invalid_officer" ",out_last_date" " FROM exchange_do_insert_aml_decision" - "($1, $2, $3, $4, $5, $6, $7, $8, $9);"); + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "do_insert_aml_decision", params, rs); GNUNET_free (notify_s); + if (NULL != kyc_s) + free (kyc_s); return qs; } diff --git a/src/exchangedb/pg_insert_aml_decision.h b/src/exchangedb/pg_insert_aml_decision.h index b539945a7..4d4ca6e54 100644 --- a/src/exchangedb/pg_insert_aml_decision.h +++ b/src/exchangedb/pg_insert_aml_decision.h @@ -36,6 +36,7 @@ * @param new_status AML decision status * @param decision_time when was the decision made * @param justification human-readable text justifying the decision + * @param kyc_requirements JSON array with KYC requirements * @param decider_pub public key of the staff member * @param decider_sig signature of the staff member * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed to make decisions right now @@ -51,6 +52,7 @@ TEH_PG_insert_aml_decision ( enum TALER_AmlDecisionState new_status, struct GNUNET_TIME_Timestamp decision_time, const char *justification, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPublicKeyP *decider_pub, const struct TALER_AmlOfficerSignatureP *decider_sig, bool *invalid_officer, diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 5f6274919..20ffaf0cd 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -2406,6 +2406,8 @@ TALER_officer_aml_query_verify ( * @param h_payto payto URI hash of the account the * decision is about * @param new_state updated AML state + * @param kyc_requirements additional KYC requirements to + * impose, can be NULL * @param officer_priv private key of AML officer * @param[out] officer_sig where to write the signature */ @@ -2416,6 +2418,7 @@ TALER_officer_aml_decision_sign ( const struct TALER_Amount *new_threshold, const struct TALER_PaytoHashP *h_payto, enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, struct TALER_AmlOfficerSignatureP *officer_sig); @@ -2430,6 +2433,8 @@ TALER_officer_aml_decision_sign ( * @param h_payto payto URI hash of the account the * decision is about * @param new_state updated AML state + * @param kyc_requirements additional KYC requirements to + * impose, can be NULL * @param officer_pub public key of AML officer * @param officer_sig signature to verify * @return #GNUNET_OK if the signature is valid @@ -2441,6 +2446,7 @@ TALER_officer_aml_decision_verify ( const struct TALER_Amount *new_threshold, const struct TALER_PaytoHashP *h_payto, enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPublicKeyP *officer_pub, const struct TALER_AmlOfficerSignatureP *officer_sig); diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 15329ad1c..5cfe6a98e 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -4608,6 +4608,7 @@ typedef void * @param h_payto payto URI hash of the account the * decision is about * @param new_state updated AML state + * @param kyc_requirements JSON array of KYC requirements being imposed, NULL for none * @param officer_priv private key of the deciding AML officer * @param cb function to call with the exchange's result * @param cb_cls closure for @a cb @@ -4622,6 +4623,7 @@ TALER_EXCHANGE_add_aml_decision ( const struct TALER_Amount *new_threshold, const struct TALER_PaytoHashP *h_payto, enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, TALER_EXCHANGE_AddAmlDecisionCallback cb, void *cb_cls); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 0a389bd6e..f4397e296 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -6704,6 +6704,7 @@ struct TALER_EXCHANGEDB_Plugin enum TALER_AmlDecisionState new_status, struct GNUNET_TIME_Timestamp decision_time, const char *justification, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPublicKeyP *decider_pub, const struct TALER_AmlOfficerSignatureP *decider_sig, bool *invalid_officer, diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index cf4279514..37d347c30 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -2741,6 +2741,7 @@ TALER_TESTING_cmd_set_officer ( * @param new_threshold new threshold to set * @param justification justification given for the decision * @param new_state new AML state for the account + * @param kyc_requirement KYC requirement to impose * @param expected_response expected HTTP return status * @return the command */ @@ -2752,6 +2753,7 @@ TALER_TESTING_cmd_take_aml_decision ( const char *new_threshold, const char *justification, enum TALER_AmlDecisionState new_state, + const char *kyc_requirement, unsigned int expected_response); diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c index 5e5383f25..7245db3b1 100644 --- a/src/lib/exchange_api_add_aml_decision.c +++ b/src/lib/exchange_api_add_aml_decision.c @@ -132,6 +132,7 @@ TALER_EXCHANGE_add_aml_decision ( const struct TALER_Amount *new_threshold, const struct TALER_PaytoHashP *h_payto, enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, TALER_EXCHANGE_AddAmlDecisionCallback cb, void *cb_cls) @@ -149,6 +150,7 @@ TALER_EXCHANGE_add_aml_decision ( new_threshold, h_payto, new_state, + kyc_requirements, officer_priv, &officer_sig); wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision); @@ -190,6 +192,9 @@ TALER_EXCHANGE_add_aml_decision ( h_payto), GNUNET_JSON_pack_uint64 ("new_state", (uint32_t) new_state), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("kyc_requirements", + (json_t *) kyc_requirements)), TALER_JSON_pack_amount ("new_threshold", new_threshold), GNUNET_JSON_pack_timestamp ("decision_time", diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c index feeed3c78..12f695690 100644 --- a/src/testing/test_kyc_api.c +++ b/src/testing/test_kyc_api.c @@ -448,6 +448,7 @@ run (void *cls, "EUR:10000", "party time", TALER_AML_NORMAL, + NULL, MHD_HTTP_FORBIDDEN), /* Check that no decision was taken, but that we are allowed to read this information */ @@ -468,6 +469,7 @@ run (void *cls, "EUR:10000", "party time", TALER_AML_NORMAL, + NULL, MHD_HTTP_NO_CONTENT), TALER_TESTING_cmd_check_aml_decisions ("check-decisions-one-normal", "create-aml-officer-1", @@ -489,6 +491,7 @@ run (void *cls, "EUR:1000", "party over", TALER_AML_FROZEN, + NULL, MHD_HTTP_NO_CONTENT), TALER_TESTING_cmd_check_aml_decisions ("check-decisions-one-frozen", "create-aml-officer-1", diff --git a/src/testing/testing_api_cmd_take_aml_decision.c b/src/testing/testing_api_cmd_take_aml_decision.c index 0992945d6..871cdb712 100644 --- a/src/testing/testing_api_cmd_take_aml_decision.c +++ b/src/testing/testing_api_cmd_take_aml_decision.c @@ -72,6 +72,11 @@ struct AmlDecisionState */ const char *justification; + /** + * KYC requirement to add. + */ + const char *kyc_requirement; + /** * Threshold transaction amount. */ @@ -133,6 +138,7 @@ take_aml_decision_run (void *cls, const struct TALER_PaytoHashP *h_payto; const struct TALER_AmlOfficerPrivateKeyP *officer_priv; const struct TALER_TESTING_Command *ref; + json_t *kyc_requirements = NULL; (void) cmd; now = GNUNET_TIME_timestamp_get (); @@ -160,6 +166,15 @@ take_aml_decision_run (void *cls, TALER_TESTING_get_trait_officer_priv (ref, &officer_priv)); ds->h_payto = *h_payto; + if (NULL != ds->kyc_requirement) + { + kyc_requirements = json_array (); + GNUNET_assert (NULL != kyc_requirements); + GNUNET_assert (0 == + json_array_append (kyc_requirements, + json_string (ds->kyc_requirement))); + } + ds->dh = TALER_EXCHANGE_add_aml_decision ( is->ctx, is->exchange_url, @@ -168,9 +183,11 @@ take_aml_decision_run (void *cls, &ds->new_threshold, h_payto, ds->new_state, + kyc_requirements, officer_priv, &take_aml_decision_cb, ds); + json_decref (kyc_requirements); if (NULL == ds->dh) { GNUNET_break (0); @@ -246,6 +263,7 @@ TALER_TESTING_cmd_take_aml_decision ( const char *new_threshold, const char *justification, enum TALER_AmlDecisionState new_state, + const char *kyc_requirement, unsigned int expected_response) { struct AmlDecisionState *ds; @@ -253,6 +271,7 @@ TALER_TESTING_cmd_take_aml_decision ( ds = GNUNET_new (struct AmlDecisionState); ds->officer_ref_cmd = ref_officer; ds->account_ref_cmd = ref_operation; + ds->kyc_requirement = kyc_requirement; if (GNUNET_OK != TALER_string_to_amount (new_threshold, &ds->new_threshold)) diff --git a/src/util/aml_signatures.c b/src/util/aml_signatures.c index cad2e7488..6d90b25c2 100644 --- a/src/util/aml_signatures.c +++ b/src/util/aml_signatures.c @@ -56,6 +56,12 @@ struct TALER_AmlDecisionPS */ struct TALER_PaytoHashP h_payto GNUNET_PACKED; + /** + * Hash over JSON array with KYC requirements that were imposed. All zeros + * for none. + */ + struct GNUNET_HashCode h_kyc_requirements; + /** * What is the new AML status? */ @@ -72,6 +78,7 @@ TALER_officer_aml_decision_sign ( const struct TALER_Amount *new_threshold, const struct TALER_PaytoHashP *h_payto, enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, struct TALER_AmlOfficerSignatureP *officer_sig) { @@ -87,6 +94,9 @@ TALER_officer_aml_decision_sign ( &ad.h_justification); TALER_amount_hton (&ad.new_threshold, new_threshold); + if (NULL != kyc_requirements) + TALER_json_hash (kyc_requirements, + &ad.h_kyc_requirements); GNUNET_CRYPTO_eddsa_sign (&officer_priv->eddsa_priv, &ad, &officer_sig->eddsa_signature); @@ -100,6 +110,7 @@ TALER_officer_aml_decision_verify ( const struct TALER_Amount *new_threshold, const struct TALER_PaytoHashP *h_payto, enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, const struct TALER_AmlOfficerPublicKeyP *officer_pub, const struct TALER_AmlOfficerSignatureP *officer_sig) { @@ -115,6 +126,9 @@ TALER_officer_aml_decision_verify ( &ad.h_justification); TALER_amount_hton (&ad.new_threshold, new_threshold); + if (NULL != kyc_requirements) + TALER_json_hash (kyc_requirements, + &ad.h_kyc_requirements); return GNUNET_CRYPTO_eddsa_verify ( TALER_SIGNATURE_AML_DECISION, &ad,