From 8951abfc505c38aaea2b709e5540e84cf7971cf4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 10 Oct 2021 17:18:24 +0200 Subject: [PATCH] -finish implemnetation of /kyc-check client library --- src/include/taler_exchange_service.h | 4 +- src/include/taler_signatures.h | 31 ++++++ src/lib/exchange_api_common.c | 2 +- src/lib/exchange_api_deposit.c | 6 +- src/lib/exchange_api_handle.c | 12 +-- src/lib/exchange_api_handle.h | 4 +- src/lib/exchange_api_kyc_check.c | 152 +++++++++++++++++++++------ 7 files changed, 162 insertions(+), 49 deletions(-) diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index ed6f2eb1a..d5cc00eaf 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1810,7 +1810,7 @@ TALER_EXCHANGE_verify_coin_history ( * were set, * #GNUNET_SYSERR if there was a protocol violation in @a history */ -int +enum GNUNET_GenericReturnValue TALER_EXCHANGE_parse_reserve_history ( struct TALER_EXCHANGE_Handle *exchange, const json_t *history, @@ -1988,7 +1988,7 @@ typedef void struct TALER_EXCHANGE_KycCheckHandle * TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *eh, uint64_t payment_target, - const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_payto, struct GNUNET_TIME_Relative timeout, TALER_EXCHANGE_KycStatusCallback cb, void *cb_cls); diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index 6b5d37685..93e48b7ce 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -167,6 +167,12 @@ */ #define TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED 1043 +/** + * Signature by which an exchange affirms that an account + * successfully passed the KYC checks. + */ +#define TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS 1044 + /**********************/ /* Auditor signatures */ @@ -818,6 +824,31 @@ struct TALER_ExchangeKeySetPS }; +/** + * @brief Signature by which an exchange affirms that an account + * successfully passed the KYC checks. + */ +struct TALER_ExchangeAccountSetupSuccessPS +{ + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS. Signed by a + * `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the payto for which the signature was + * made. + */ + struct GNUNET_HashCode h_payto; + + /** + * When was the signature made. + */ + struct GNUNET_TIME_AbsoluteNBO timestamp; +}; + + /** * @brief Signature made by the exchange offline key over the information of * an auditor to be added to the exchange's set of auditors. diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index da1400b9d..a6873c656 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -42,7 +42,7 @@ * were set, * #GNUNET_SYSERR if there was a protocol violation in @a history */ -int +enum GNUNET_GenericReturnValue TALER_EXCHANGE_parse_reserve_history ( struct TALER_EXCHANGE_Handle *exchange, const json_t *history, diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index 86f5034aa..b945f6b76 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -188,7 +188,7 @@ auditor_cb (void *cls, * @param[out] exchange_pub set to the exchange's public key * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not */ -static int +static enum GNUNET_GenericReturnValue verify_deposit_signature_ok (struct TALER_EXCHANGE_DepositHandle *dh, const json_t *json, struct TALER_ExchangeSignatureP *exchange_sig, @@ -245,7 +245,7 @@ verify_deposit_signature_ok (struct TALER_EXCHANGE_DepositHandle *dh, * @param json json reply with the signature(s) and transaction history * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not */ -static int +static enum GNUNET_GenericReturnValue verify_deposit_signature_conflict ( const struct TALER_EXCHANGE_DepositHandle *dh, const json_t *json) @@ -441,7 +441,7 @@ handle_deposit_finished (void *cls, * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not */ -static int +static enum GNUNET_GenericReturnValue verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, const struct TALER_Amount *amount, const struct GNUNET_HashCode *h_wire, diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index dfd5a3dc6..142ed18e6 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -257,7 +257,7 @@ free_keys_request (struct KeysRequest *kr) * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ -static int +static enum GNUNET_GenericReturnValue parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, int check_sigs, json_t *sign_key_obj, @@ -317,7 +317,7 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ -static int +static enum GNUNET_GenericReturnValue parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, int check_sigs, json_t *denom_key_obj, @@ -402,7 +402,7 @@ EXITIF_exit: * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ -static int +static enum GNUNET_GenericReturnValue parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, int check_sigs, json_t *auditor_obj, @@ -670,7 +670,7 @@ denoms_cmp (struct TALER_EXCHANGE_DenomPublicKey *denom1, * @return #GNUNET_OK on success, #GNUNET_SYSERR on error * (malformed JSON) */ -static int +static enum GNUNET_GenericReturnValue decode_keys_json (const json_t *resp_obj, bool check_sig, struct TALER_EXCHANGE_Keys *key_data, @@ -1314,7 +1314,7 @@ TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) * @param h the exchange handle to query * @return #GNUNET_YES if we are ready, #GNUNET_NO if not */ -int +enum GNUNET_GenericReturnValue TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) { return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; @@ -1352,7 +1352,7 @@ TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, * @param at where to write the result * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue parse_date_string (const char *dateline, struct GNUNET_TIME_Absolute *at) { diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h index 1a7e8ee7b..df0ccf7fc 100644 --- a/src/lib/exchange_api_handle.h +++ b/src/lib/exchange_api_handle.h @@ -229,7 +229,7 @@ TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h); * @param h the exchange handle to query * @return #GNUNET_YES if we are ready, #GNUNET_NO if not */ -int +enum GNUNET_GenericReturnValue TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h); /** @@ -238,7 +238,7 @@ TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h); * @param h the exchange handle to query * @return #GNUNET_YES if we are ready, #GNUNET_NO if not */ -int +enum GNUNET_GenericReturnValue TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h); diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c index 5b9d10164..82fddd455 100644 --- a/src/lib/exchange_api_kyc_check.c +++ b/src/lib/exchange_api_kyc_check.c @@ -61,6 +61,10 @@ struct TALER_EXCHANGE_KycCheckHandle */ void *cb_cls; + /** + * Hash of the payto:// URL that is being KYC'ed. + */ + struct GNUNET_HashCode h_payto; }; @@ -90,30 +94,104 @@ handle_kyc_check_finished (void *cls, ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: - GNUNET_break (0); // FIXME - TALER_EXCHANGE_kyc_check_cancel (kch); - return; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &ks.details.kyc_ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &ks.details.kyc_ok.exchange_pub), + TALER_JSON_spec_absolute_time ("now", + &ks.details.kyc_ok.timestamp), + GNUNET_JSON_spec_end () + }; + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = { + .purpose.size = htonl (sizeof (kyc_purpose)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS), + .h_payto = kch->h_payto + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + kyc_purpose.timestamp = GNUNET_TIME_absolute_hton ( + ks.details.kyc_ok.timestamp); + key_state = TALER_EXCHANGE_get_keys (kch->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &ks.details.kyc_ok.exchange_pub)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_JSON_parse_free (spec); + break; + } + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS, + &kyc_purpose, + &ks.details.kyc_ok.exchange_sig.eddsa_signature, + &ks.details.kyc_ok.exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_JSON_parse_free (spec); + break; + } + kch->cb (kch->cb_cls, + &ks); + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } case MHD_HTTP_ACCEPTED: - GNUNET_break (0); // FIXME - TALER_EXCHANGE_kyc_check_cancel (kch); - return; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("kyc_url", + &ks.details.kyc_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + kch->cb (kch->cb_cls, + &ks); + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } case MHD_HTTP_NO_CONTENT: - GNUNET_break (0); // FIXME - TALER_EXCHANGE_kyc_check_cancel (kch); - return; + break; case MHD_HTTP_BAD_REQUEST: ks.ec = TALER_JSON_get_error_code (j); /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_UNAUTHORIZED: - GNUNET_break (0); // FIXME - TALER_EXCHANGE_kyc_check_cancel (kch); - return; + ks.ec = TALER_JSON_get_error_code (j); + break; case MHD_HTTP_NOT_FOUND: ks.ec = TALER_JSON_get_error_code (j); - TALER_EXCHANGE_kyc_check_cancel (kch); - return; + break; case MHD_HTTP_INTERNAL_SERVER_ERROR: ks.ec = TALER_JSON_get_error_code (j); /* Server had an internal issue; we should retry, but this API @@ -129,27 +207,16 @@ handle_kyc_check_finished (void *cls, (int) ks.ec); break; } + kch->cb (kch->cb_cls, + &ks); TALER_EXCHANGE_kyc_check_cancel (kch); } -/** - * Submit a kyc_check request to the exchange and get the exchange's response. - * - * This API is typically not used by anyone, it is more a threat against those - * trying to receive a funds transfer by abusing the refresh protocol. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param coin_priv private key to request kyc_check data for - * @param kyc_check_cb the callback to call with the useful result of the - * refresh operation the @a coin_priv was involved in (if any) - * @param kyc_check_cb_cls closure for @a kyc_check_cb - * @return a handle for this request - */ struct TALER_EXCHANGE_KycCheckHandle * TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange, uint64_t payment_target, - const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_payto, struct GNUNET_TIME_Relative timeout, TALER_EXCHANGE_KycStatusCallback cb, void *cb_cls) @@ -157,6 +224,7 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange, struct TALER_EXCHANGE_KycCheckHandle *kch; CURL *eh; struct GNUNET_CURL_Context *ctx; + char *arg_str; if (GNUNET_YES != TEAH_handle_is_ready (exchange)) @@ -164,13 +232,33 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange, GNUNET_break (0); return NULL; } + { + char payto_str[sizeof (*h_payto) * 2]; + char *end; + unsigned long long timeout_ms; + end = GNUNET_STRINGS_data_to_string ( + h_payto, + sizeof (*h_payto), + payto_str, + sizeof (payto_str) - 1); + *end = '\0'; + timeout_ms = timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + GNUNET_asprintf (&arg_str, + "/kyc-check/%llu?h_payto=%s&timeout_ms=%llu", + (unsigned long long) payment_target, + payto_str, + timeout_ms); + } kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); kch->exchange = exchange; + kch->h_payto = *h_payto; kch->cb = cb; kch->cb_cls = cb_cls; kch->url = TEAH_path_to_url (exchange, - "FIXME"); + arg_str); + GNUNET_free (arg_str); if (NULL == kch->url) { GNUNET_free (kch); @@ -193,12 +281,6 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange, } -/** - * Cancel a kyc_check request. This function cannot be used - * on a request handle if the callback was already invoked. - * - * @param kch the kyc_check handle - */ void TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch) {