From 899f2b4070ec04af0e0040f6b7fa62d3d860ffee Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 14 Sep 2015 15:29:40 +0200 Subject: [PATCH] implement /wire api (#3947) --- src/include/taler_mint_service.h | 82 +++++ src/mint-lib/Makefile.am | 1 + src/mint-lib/mint_api_wire.c | 611 +++++++++++++++++++++++++++++++ 3 files changed, 694 insertions(+) create mode 100644 src/mint-lib/mint_api_wire.c diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h index 02407a3a9..3f89b0745 100644 --- a/src/include/taler_mint_service.h +++ b/src/include/taler_mint_service.h @@ -353,6 +353,88 @@ TALER_MINT_get_denomination_key (const struct TALER_MINT_Keys *keys, const struct TALER_DenominationPublicKey *pk); +/* ********************* /wire *********************** */ + + +/** + * @brief A Wire format inquiry handle + */ +struct TALER_MINT_WireHandle; + + +/** + * Callbacks of this type are used to serve the result of submitting a + * wire format inquiry request to a mint. + * + * The callback is invoked multiple times, once for each supported @a + * method. Finally, it is invoked one more time with cls/0/NULL/NULL + * to indicate the end of the iteration. If any request fails to + * generate a valid response from the mint, @a http_status will also + * be zero and the iteration will also end. Thus, the iteration + * always ends with a final call with an @a http_status of 0. If the + * @a http_status is already 0 on the first call, then the response to + * the /wire request was invalid. Later, clients can tell the + * difference between @a http_status of 0 indicating a failed + * /wire/method request and a regular end of the iteration by @a + * method being non-NULL. If the mint simply correctly asserts that + * it does not support any methods, @a method will be NULL but the @a + * http_status will be #MHD_HTTP_OK for the first call (followed by a + * cls/0/NULL/NULL call to signal the end of the iteration). + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful request; + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param method wire format method supported, i.e. "test" or "sepa", or NULL + * if already the /wire request failed. + * @param obj the received JSON reply, if successful this should be the wire + * format details as provided by /wire/METHOD/, or NULL if the + * reply was not in JSON format (in this case, the client might + * want to do an HTTP request to /wire/METHOD/ with a browser to + * provide more information to the user about the @a method). + */ +typedef void +(*TALER_MINT_WireResultCallback) (void *cls, + unsigned int http_status, + const char *method, + json_t *obj); + + +/** + * Obtain information about a mint's wire instructions. + * A mint may provide wire instructions for creating + * a reserve. The wire instructions also indicate + * which wire formats merchants may use with the mint. + * This API is typically used by a wallet for wiring + * funds, and possibly by a merchant to determine + * supported wire formats. + * + * Note that while we return the (main) response verbatim to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid). If the mint's reply is not well-formed, + * we return an HTTP status code of zero to @a cb. + * + * @param mint the mint handle; the mint must be ready to operate + * @param wire_cb the callback to call when a reply for this request is available + * @param wire_cb_cls closure for the above callback + * @return a handle for this request + */ +struct TALER_MINT_WireHandle * +TALER_MINT_wire (struct TALER_MINT_Handle *mint, + TALER_MINT_WireResultCallback wire_cb, + void *wire_cb_cls); + + +/** + * Cancel a wire information request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param wh the wire information request handle + */ +void +TALER_MINT_wire_cancel (struct TALER_MINT_WireHandle *wh); + + /* ********************* /deposit *********************** */ diff --git a/src/mint-lib/Makefile.am b/src/mint-lib/Makefile.am index 0cb83dacb..2729177f8 100644 --- a/src/mint-lib/Makefile.am +++ b/src/mint-lib/Makefile.am @@ -22,6 +22,7 @@ libtalermint_la_SOURCES = \ mint_api_deposit.c \ mint_api_refresh.c \ mint_api_refresh_link.c \ + mint_api_wire.c \ mint_api_withdraw.c libtalermint_la_LIBADD = \ diff --git a/src/mint-lib/mint_api_wire.c b/src/mint-lib/mint_api_wire.c new file mode 100644 index 000000000..7641af7ad --- /dev/null +++ b/src/mint-lib/mint_api_wire.c @@ -0,0 +1,611 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see + +*/ +/** + * @file mint-lib/mint_api_wire.c + * @brief Implementation of the /wire request of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include "taler_mint_service.h" +#include "mint_api_common.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * @brief A Wire Handle + */ +struct TALER_MINT_WireHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * Function to call with the result. + */ + TALER_MINT_WireResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + struct MAC_DownloadBuffer db; + + /** + * Set to the "methods" JSON array returned by the + * /wire request. + */ + json_t *methods; + + /** + * Current iteration offset in the @e methods array. + */ + unsigned int methods_off; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * for /wire/sepa from the mint is valid. + * + * @param wh wire handle + * @param json json reply with the signature + * @return #GNUNET_SYSERR if @a json is invalid, + * #GNUNET_NO if the method is unknown, + * #GNUNET_OK if the json is valid + */ +static int +verify_wire_sepa_signature_ok (const struct TALER_MINT_WireHandle *wh, + json_t *json) +{ + struct TALER_MasterSignatureP mint_sig; + struct TALER_MasterWireSepaDetailsPS mp; + const char *receiver_name; + const char *iban; + const char *bic; + const struct TALER_MINT_Keys *key_state; + struct GNUNET_HashContext *hc; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("sig", &mint_sig), + MAJ_spec_string ("receiver_name", &receiver_name), + MAJ_spec_string ("iban", &iban), + MAJ_spec_string ("bic", &bic), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + key_state = TALER_MINT_get_keys (wh->mint); + mp.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SEPA_DETAILS); + mp.purpose.size = htonl (sizeof (struct TALER_MasterWireSepaDetailsPS)); + hc = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hc, + receiver_name, + strlen (receiver_name) + 1); + GNUNET_CRYPTO_hash_context_read (hc, + iban, + strlen (iban) + 1); + GNUNET_CRYPTO_hash_context_read (hc, + bic, + strlen (bic) + 1); + GNUNET_CRYPTO_hash_context_finish (hc, + &mp.h_sepa_details); + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SEPA_DETAILS, + &mp.purpose, + &mint_sig.eddsa_signature, + &key_state->master_pub.eddsa_pub)) + { + GNUNET_break_op (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + MAJ_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Verify that the signature on the "200 OK" response + * for /wire/METHOD from the mint is valid. + * + * @param wh wire handle with key material + * @param method method to verify the reply for + * @param json json reply with the signature + * @return #GNUNET_SYSERR if @a json is invalid, + * #GNUNET_NO if the method is unknown, + * #GNUNET_OK if the json is valid + */ +static int +verify_wire_method_signature_ok (const struct TALER_MINT_WireHandle *wh, + const char *method, + json_t *json) +{ + struct + { + /** + * Name fo the method. + */ + const char *method; + + /** + * Handler to invoke to verify signature. + * + * @param wh wire handle with key material + * @param json json reply with signature to verify + */ + int (*handler)(const struct TALER_MINT_WireHandle *wh, + json_t *json); + } handlers[] = { + { "sepa", &verify_wire_sepa_signature_ok }, + { NULL, NULL } + }; + unsigned int i; + + for (i=0;NULL != handlers[i].method; i++) + if (0 == strcasecmp (handlers[i].method, + method)) + return handlers[i].handler (wh, + json); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Wire transfer method `%s' not supported\n", + method); + return GNUNET_NO; +} + + +/** + * Perform the next /wire/method request or signal + * the end of the iteration. + * + * @param wh the wire handle + * @return a handle for this request + */ +static void +request_wire_method (struct TALER_MINT_WireHandle *wh); + + +/** + * Function called when we're done processing the + * HTTP /wire/METHOD request. + * + * @param cls the `struct TALER_MINT_WireHandle` + * @param eh the curl request handle + */ +static void +handle_wire_method_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_WireHandle *wh = cls; + long response_code; + json_t *json; + + wh->job = NULL; + json = MAC_download_get_result (&wh->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + const char *method; + + method = json_string_value (json_array_get (wh->methods, + wh->methods_off - 1)); + if (GNUNET_OK != + verify_wire_method_signature_ok (wh, + method, + json)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + break; + } + case MHD_HTTP_FOUND: + /* /wire/test returns a 302 redirect, we should just give + this information back to the callback below */ + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (0 == response_code) + { + /* signal end of iteration */ + wh->cb (wh->cb_cls, + 0, + NULL, + NULL); + json_decref (json); + TALER_MINT_wire_cancel (wh); + } + /* pass on successful reply */ + wh->cb (wh->cb_cls, + response_code, + NULL, + json); + /* trigger request for the next /wire/method */ + request_wire_method (wh); +} + + +/** + * Perform the next /wire/method request or signal + * the end of the iteration. + * + * @param wh the wire handle + * @return a handle for this request + */ +static void +request_wire_method (struct TALER_MINT_WireHandle *wh) +{ + struct TALER_MINT_Context *ctx; + CURL *eh; + char *path; + + if (json_array_size (wh->methods) <= wh->methods_off) + { + /* we are done, signal end of iteration */ + wh->cb (wh->cb_cls, + 0, + NULL, + NULL); + TALER_MINT_wire_cancel (wh); + return; + } + GNUNET_free_non_null (wh->db.buf); + wh->db.buf = NULL; + wh->db.buf_size = 0; + wh->db.eno = 0; + GNUNET_free_non_null (wh->url); + GNUNET_asprintf (&path, + "/wire/%s", + json_string_value (json_array_get (wh->methods, + wh->methods_off++))); + wh->url = MAH_path_to_url (wh->mint, + path); + GNUNET_free (path); + + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + wh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &wh->db)); + ctx = MAH_handle_to_context (wh->mint); + wh->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_wire_method_finished, + wh); +} + + +/** + * Verify that the signature on the "200 OK" response + * for /wire from the mint is valid. + * + * @param wh wire handle + * @param json json reply with the signature + * @return NULL if @a json is invalid, otherwise the + * "methods" array (with an RC of 1) + */ +static json_t * +verify_wire_signature_ok (const struct TALER_MINT_WireHandle *wh, + json_t *json) +{ + struct TALER_MintSignatureP mint_sig; + struct TALER_MintPublicKeyP mint_pub; + struct TALER_MintWireSupportMethodsPS mp; + json_t *methods; + const struct TALER_MINT_Keys *key_state; + struct GNUNET_HashContext *hc; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("sig", &mint_sig), + MAJ_spec_fixed_auto ("pub", &mint_pub), + MAJ_spec_json ("methods", &methods), + MAJ_spec_end + }; + unsigned int i; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return NULL; + } + if (! json_is_array (methods)) + { + GNUNET_break_op (0); + MAJ_parse_free (spec); + return NULL; + } + + key_state = TALER_MINT_get_keys (wh->mint); + if (GNUNET_OK != + TALER_MINT_test_signing_key (key_state, + &mint_pub)) + { + GNUNET_break_op (0); + return NULL; + } + hc = GNUNET_CRYPTO_hash_context_start (); + for (i=0;ijob = NULL; + json = MAC_download_get_result (&wh->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + json_t *methods; + + if (NULL == + (methods = verify_wire_signature_ok (wh, + json))) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + wh->methods = methods; + request_wire_method (wh); + return; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the mint is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (0 != response_code) + { + /* pass on successful reply */ + wh->cb (wh->cb_cls, + response_code, + NULL, + json); + } + /* signal end of iteration */ + wh->cb (wh->cb_cls, + 0, + NULL, + NULL); + json_decref (json); + TALER_MINT_wire_cancel (wh); +} + + +/** + * Obtain information about a mint's wire instructions. + * A mint may provide wire instructions for creating + * a reserve. The wire instructions also indicate + * which wire formats merchants may use with the mint. + * This API is typically used by a wallet for wiring + * funds, and possibly by a merchant to determine + * supported wire formats. + * + * Note that while we return the (main) response verbatim to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid). If the mint's reply is not well-formed, + * we return an HTTP status code of zero to @a cb. + * + * @param mint the mint handle; the mint must be ready to operate + * @param wire_cb the callback to call when a reply for this request is available + * @param wire_cb_cls closure for the above callback + * @return a handle for this request + */ +struct TALER_MINT_WireHandle * +TALER_MINT_wire (struct TALER_MINT_Handle *mint, + TALER_MINT_WireResultCallback wire_cb, + void *wire_cb_cls) +{ + struct TALER_MINT_WireHandle *wh; + struct TALER_MINT_Context *ctx; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + wh = GNUNET_new (struct TALER_MINT_WireHandle); + wh->mint = mint; + wh->cb = wire_cb; + wh->cb_cls = wire_cb_cls; + wh->url = MAH_path_to_url (mint, "/wire"); + + eh = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + wh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &wh->db)); + ctx = MAH_handle_to_context (mint); + wh->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_wire_finished, + wh); + return wh; +} + + +/** + * Cancel a wire information request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param wire the wire information request handle + */ +void +TALER_MINT_wire_cancel (struct TALER_MINT_WireHandle *wire) +{ + if (NULL != wire->job) + { + MAC_job_cancel (wire->job); + wire->job = NULL; + } + if (NULL != wire->methods) + { + json_decref (wire->methods); + wire->methods = NULL; + } + GNUNET_free_non_null (wire->db.buf); + GNUNET_free (wire->url); + GNUNET_free (wire); +} + + +/* end of mint_api_wire.c */