diff options
Diffstat (limited to 'src/restclient')
-rw-r--r-- | src/restclient/Makefile.am | 42 | ||||
-rw-r--r-- | src/restclient/anastasis_api_config.c | 317 | ||||
-rw-r--r-- | src/restclient/anastasis_api_curl_defaults.c | 46 | ||||
-rw-r--r-- | src/restclient/anastasis_api_curl_defaults.h | 38 | ||||
-rw-r--r-- | src/restclient/anastasis_api_keyshare_lookup.c | 508 | ||||
-rw-r--r-- | src/restclient/anastasis_api_policy_lookup.c | 356 | ||||
-rw-r--r-- | src/restclient/anastasis_api_policy_store.c | 527 | ||||
-rw-r--r-- | src/restclient/anastasis_api_truth_store.c | 354 |
8 files changed, 2188 insertions, 0 deletions
diff --git a/src/restclient/Makefile.am b/src/restclient/Makefile.am new file mode 100644 index 0000000..075d3a7 --- /dev/null +++ b/src/restclient/Makefile.am @@ -0,0 +1,42 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +.NOTPARALLEL: + +lib_LTLIBRARIES = \ + libanastasisrest.la + +libanastasisrest_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libanastasisrest_la_SOURCES = \ + anastasis_api_config.c \ + anastasis_api_policy_store.c \ + anastasis_api_truth_store.c \ + anastasis_api_policy_lookup.c \ + anastasis_api_keyshare_lookup.c \ + anastasis_api_curl_defaults.c anastasis_api_curl_defaults.h +libanastasisrest_la_LIBADD = \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + -ltalerjson \ + -ltalerutil \ + -ltalermerchant \ + -ltalerjson \ + $(XLIB) + +if HAVE_LIBCURL +libanastasisrest_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libanastasisrest_la_LIBADD += -lgnurl +endif +endif + diff --git a/src/restclient/anastasis_api_config.c b/src/restclient/anastasis_api_config.c new file mode 100644 index 0000000..cf0846b --- /dev/null +++ b/src/restclient/anastasis_api_config.c @@ -0,0 +1,317 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021 Taler Systems SA + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/anastasis_api_config.c + * @brief Implementation of the /config GET + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <gnunet/gnunet_json_lib.h> +#include <taler/taler_json_lib.h> + + +/** + * Which version of the Taler protocol is implemented + * by this library? Used to determine compatibility. + */ +#define ANASTASIS_PROTOCOL_CURRENT 0 + +/** + * How many versions are we backwards compatible with? + */ +#define ANASTASIS_PROTOCOL_AGE 0 + + +struct ANASTASIS_ConfigOperation +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The callback to pass the backend response to + */ + ANASTASIS_ConfigCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /config request. + * + * @param cls the `struct ANASTASIS_ConfigOperation` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_config_finished (void *cls, + long response_code, + const void *response) +{ + struct ANASTASIS_ConfigOperation *co = cls; + const json_t *json = response; + + co->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend `%s' failed to respond to GET /config\n", + co->url); + break; + case MHD_HTTP_OK: + { + const char *name; + struct ANASTASIS_Config acfg; + json_t *methods; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_string ("business_name", + &acfg.business_name), + GNUNET_JSON_spec_string ("version", + &acfg.version), + GNUNET_JSON_spec_string ("currency", + &acfg.currency), + GNUNET_JSON_spec_json ("methods", + &methods), + GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", + &acfg.storage_limit_in_megabytes), + TALER_JSON_spec_amount_any ("annual_fee", + &acfg.annual_fee), + TALER_JSON_spec_amount_any ("truth_upload_fee", + &acfg.truth_upload_fee), + TALER_JSON_spec_amount_any ("liability_limit", + &acfg.liability_limit), + GNUNET_JSON_spec_fixed_auto ("server_salt", + &acfg.salt), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + if (0 != strcmp (name, + "anastasis")) + { + GNUNET_JSON_parse_free (spec); + response_code = 0; + break; + } + { + unsigned int age; + unsigned int revision; + unsigned int current; + char dummy; + + if (3 != sscanf (acfg.version, + "%u:%u:%u%c", + ¤t, + &revision, + &age, + &dummy)) + { + GNUNET_break_op (0); + response_code = 0; + GNUNET_JSON_parse_free (spec); + break; + } + if ( (ANASTASIS_PROTOCOL_CURRENT < current) && + (ANASTASIS_PROTOCOL_CURRENT < current - age) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider protocol version too new\n"); + response_code = 0; + GNUNET_JSON_parse_free (spec); + break; + } + if ( (ANASTASIS_PROTOCOL_CURRENT > current) && + (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > current) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider protocol version too old\n"); + GNUNET_break_op (0); + response_code = 0; + GNUNET_JSON_parse_free (spec); + break; + } + } + if ( (GNUNET_OK != + TALER_amount_cmp_currency (&acfg.liability_limit, + &acfg.annual_fee)) || + (0 != + strcasecmp (acfg.currency, + acfg.annual_fee.currency)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + break; + } + + if (! json_is_array (methods)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + break; + } + acfg.methods_length = json_array_size (methods); + { + struct ANASTASIS_AuthorizationMethodConfig mcfg[GNUNET_NZL ( + acfg.methods_length)]; + + for (unsigned int i = 0; i<acfg.methods_length; i++) + { + struct ANASTASIS_AuthorizationMethodConfig *m = &mcfg[i]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("type", + &m->type), + TALER_JSON_spec_amount_any ("cost", + &m->usage_fee), + GNUNET_JSON_spec_end () + }; + + if ( (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (methods, + i), + spec, + NULL, NULL)) || + (0 != + strcasecmp (m->usage_fee.currency, + acfg.currency)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + goto end; + } + } + acfg.methods = mcfg; + co->cb (co->cb_cls, + MHD_HTTP_OK, + &acfg); + GNUNET_JSON_parse_free (spec); + ANASTASIS_config_cancel (co); + return; + } + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + 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", + (unsigned int) response_code); + GNUNET_break_op (0); + break; + } +end: + co->cb (co->cb_cls, + response_code, + NULL); + ANASTASIS_config_cancel (co); +} + + +struct ANASTASIS_ConfigOperation * +ANASTASIS_get_config (struct GNUNET_CURL_Context *ctx, + const char *base_url, + ANASTASIS_ConfigCallback cb, + void *cb_cls) +{ + struct ANASTASIS_ConfigOperation *co; + + co = GNUNET_new (struct ANASTASIS_ConfigOperation); + co->url = TALER_url_join (base_url, + "config", + NULL); + co->ctx = ctx; + co->cb = cb; + co->cb_cls = cb_cls; + { + CURL *eh; + + eh = ANASTASIS_curl_easy_get_ (co->url); + co->job = GNUNET_CURL_job_add2 (ctx, + eh, + GNUNET_NO, + &handle_config_finished, + co); + } + if (NULL == co->job) + { + GNUNET_free (co->url); + GNUNET_free (co); + return NULL; + } + return co; +} + + +void +ANASTASIS_config_cancel (struct ANASTASIS_ConfigOperation *co) +{ + if (NULL != co->job) + { + GNUNET_CURL_job_cancel (co->job); + co->job = NULL; + } + GNUNET_free (co->url); + GNUNET_free (co); +} + + +/* end of anastasis_api_config.c */ diff --git a/src/restclient/anastasis_api_curl_defaults.c b/src/restclient/anastasis_api_curl_defaults.c new file mode 100644 index 0000000..27b965f --- /dev/null +++ b/src/restclient/anastasis_api_curl_defaults.c @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 Taler Systems SA + + 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, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/anastasis_api_curl_defaults.c + * @brief curl easy handle defaults + * @author Florian Dold + */ +#include "platform.h" +#include "anastasis_api_curl_defaults.h" + +CURL * +ANASTASIS_curl_easy_get_ (const char *url) +{ + CURL *eh; + + eh = curl_easy_init (); + if (NULL == eh) + return NULL; + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_FOLLOWLOCATION, + 1L)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); + return eh; +} diff --git a/src/restclient/anastasis_api_curl_defaults.h b/src/restclient/anastasis_api_curl_defaults.h new file mode 100644 index 0000000..62e0081 --- /dev/null +++ b/src/restclient/anastasis_api_curl_defaults.h @@ -0,0 +1,38 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 Taler Systems SA + + 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, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/anastasis_api_curl_defaults.h + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#ifndef _ANASTASIS_API_CURL_DEFAULTS_H +#define _ANASTASIS_API_CURL_DEFAULTS_H + +#include <gnunet/gnunet_curl_lib.h> + + +/** + * Get a curl handle with the right defaults + * for the exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +ANASTASIS_curl_easy_get_ (const char *url); + +#endif /* _TALER_CURL_DEFAULTS_H */ diff --git a/src/restclient/anastasis_api_keyshare_lookup.c b/src/restclient/anastasis_api_keyshare_lookup.c new file mode 100644 index 0000000..a2e4316 --- /dev/null +++ b/src/restclient/anastasis_api_keyshare_lookup.c @@ -0,0 +1,508 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021 Taler Systems SA + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/anastasis_api_keyshare_lookup.c + * @brief Implementation of the GET /truth client + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * @brief A Contract Operation Handle + */ +struct ANASTASIS_KeyShareLookupOperation +{ + /** + * The url for this request, including parameters. + */ + char *url; + + /** + * The url for this request, without response parameter. + */ + char *display_url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_KeyShareLookupCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Identification of the Truth Object + */ + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_public_key; + + /** + * Key to decrypt the truth on the server + */ + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key; + + /** + * Hash of the response (security question) + */ + const struct GNUNET_HashCode *hashed_answer; + + /** + * Payment URI we received from the service, or NULL. + */ + char *pay_uri; + + /** + * Location URI we received from the service, or NULL. + */ + char *location; + + /** + * Content type of the body. + */ + char *content_type; +}; + + +void +ANASTASIS_keyshare_lookup_cancel ( + struct ANASTASIS_KeyShareLookupOperation *kslo) +{ + if (NULL != kslo->job) + { + GNUNET_CURL_job_cancel (kslo->job); + kslo->job = NULL; + } + GNUNET_free (kslo->location); + GNUNET_free (kslo->pay_uri); + GNUNET_free (kslo->display_url); + GNUNET_free (kslo->url); + GNUNET_free (kslo->content_type); + GNUNET_free (kslo); +} + + +/** + * Process GET /truth response + * + * @param cls our `struct ANASTASIS_KeyShareLookupOperation *` + * @param response_code the HTTP status + * @param data the body of the response + * @param data_size number of bytes in @a data + */ +static void +handle_keyshare_lookup_finished (void *cls, + long response_code, + const void *data, + size_t data_size) +{ + struct ANASTASIS_KeyShareLookupOperation *kslo = cls; + struct ANASTASIS_KeyShareDownloadDetails kdd; + + kslo->job = NULL; + memset (&kdd, + 0, + sizeof (kdd)); + kdd.server_url = kslo->display_url; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from GET /truth\n"); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + break; + case MHD_HTTP_OK: + if (sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP) != data_size) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + break; + } + /* Success, call callback with all details! */ + memcpy (&kdd.details.eks, + data, + data_size); + kslo->cb (kslo->cb_cls, + &kdd); + ANASTASIS_keyshare_lookup_cancel (kslo); + return; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + kdd.status = ANASTASIS_KSD_CLIENT_FAILURE; + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == kslo->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (kslo->pay_uri, + &pd)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse `%s'\n", + kslo->pay_uri); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + break; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &kdd.details.payment_required.payment_secret, + sizeof (kdd.details.payment_required.payment_secret))) + { + GNUNET_break (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + kdd.status = ANASTASIS_KSD_PAYMENT_REQUIRED; + kdd.details.payment_required.taler_pay_uri = kslo->pay_uri; + kslo->cb (kslo->cb_cls, + &kdd); + ANASTASIS_keyshare_lookup_cancel (kslo); + TALER_MERCHANT_parse_pay_uri_free (&pd); + return; + } + break; + case MHD_HTTP_SEE_OTHER: + /* Nothing really to verify, authentication required/failed */ + kdd.status = ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION; + kdd.details.redirect_url = kslo->location; + kslo->cb (kslo->cb_cls, + &kdd); + ANASTASIS_keyshare_lookup_cancel (kslo); + return; + case MHD_HTTP_ALREADY_REPORTED: + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, authentication required/failed */ + { + kdd.status = ANASTASIS_KSD_INVALID_ANSWER; + kdd.details.open_challenge.body = data; + kdd.details.open_challenge.body_size = data_size; + kdd.details.open_challenge.content_type = kslo->content_type; + kdd.details.open_challenge.http_status = response_code; + kslo->cb (kslo->cb_cls, + &kdd); + } + ANASTASIS_keyshare_lookup_cancel (kslo); + return; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; + break; + case MHD_HTTP_GONE: + /* Nothing really to verify */ + kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; + break; + case MHD_HTTP_EXPECTATION_FAILED: + /* Nothing really to verify */ + kdd.status = ANASTASIS_KSD_CLIENT_FAILURE; + break; + case MHD_HTTP_TOO_MANY_REQUESTS: + kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, + data_size); + kdd.details.server_failure.http_status = response_code; + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to GET /truth\n", + (unsigned int) response_code); + GNUNET_break (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, + data_size); + kdd.details.server_failure.http_status = response_code; + break; + } + kslo->cb (kslo->cb_cls, + &kdd); + ANASTASIS_keyshare_lookup_cancel (kslo); +} + + +/** + * Patch value in @a val, replacing new line with '\0'. + * + * @param[in,out] 0-terminated string to replace '\n'/'\r' with '\0' in. + */ +static void +patch_value (char *val) +{ + size_t len; + + /* found location URI we care about! */ + len = strlen (val); + while ( (len > 0) && + ( ('\n' == val[len - 1]) || + ('\r' == val[len - 1]) ) ) + { + len--; + val[len] = '\0'; + } +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_StorePolicyOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_KeyShareLookupOperation *kslo = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_TALER)) + { + /* found payment URI we care about! */ + GNUNET_free (kslo->pay_uri); + kslo->pay_uri = GNUNET_strdup (hdr_val); + patch_value (kslo->pay_uri); + } + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_LOCATION)) + { + /* found location URI we care about! */ + GNUNET_free (kslo->location); + kslo->location = GNUNET_strdup (hdr_val); + patch_value (kslo->location); + } + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONTENT_TYPE)) + { + /* found location URI we care about! */ + GNUNET_free (kslo->content_type); + kslo->content_type = GNUNET_strdup (hdr_val); + patch_value (kslo->content_type); + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_KeyShareLookupOperation * +ANASTASIS_keyshare_lookup ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, + const struct ANASTASIS_PaymentSecretP *payment_secret, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_HashCode *hashed_answer, + ANASTASIS_KeyShareLookupCallback cb, + void *cb_cls) +{ + struct ANASTASIS_KeyShareLookupOperation *kslo; + CURL *eh; + struct curl_slist *job_headers; + char *path; + char *answer_s; + unsigned long long tms; + + tms = (unsigned long long) (timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + job_headers = NULL; + { + struct curl_slist *ext; + char *val; + char *hdr; + + /* Set Truth-Decryption-Key header */ + val = GNUNET_STRINGS_data_to_string_alloc (truth_key, + sizeof (*truth_key)); + GNUNET_asprintf (&hdr, + "%s: %s", + ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY, + val); + GNUNET_free (val); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + + /* Setup Payment-Identifier header */ + if (NULL != payment_secret) + { + char *paid_order_id; + + paid_order_id = GNUNET_STRINGS_data_to_string_alloc ( + payment_secret, + sizeof (*payment_secret)); + GNUNET_asprintf (&hdr, + "%s: %s", + ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER, + paid_order_id); + GNUNET_free (paid_order_id); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + } + } + kslo = GNUNET_new (struct ANASTASIS_KeyShareLookupOperation); + kslo->ctx = ctx; + kslo->truth_key = truth_key; + { + char *uuid_str; + + uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, + sizeof (*truth_uuid)); + GNUNET_asprintf (&path, + "truth/%s", + uuid_str); + GNUNET_free (uuid_str); + } + { + char timeout_ms[32]; + + GNUNET_snprintf (timeout_ms, + sizeof (timeout_ms), + "%llu", + tms); + if (NULL != hashed_answer) + { + answer_s = GNUNET_STRINGS_data_to_string_alloc (hashed_answer, + sizeof (*hashed_answer)); + kslo->url = TALER_url_join (backend_url, + path, + "response", + answer_s, + "timeout_ms", + (0 != timeout.rel_value_us) + ? timeout_ms + : NULL, + NULL); + GNUNET_free (answer_s); + } + else + { + kslo->url = TALER_url_join (backend_url, + path, + "timeout_ms", + (0 != timeout.rel_value_us) + ? timeout_ms + : NULL, + NULL); + } + } + kslo->display_url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (kslo->url); + if (0 != tms) + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 5000))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + kslo)); + kslo->cb = cb; + kslo->cb_cls = cb_cls; + kslo->job = GNUNET_CURL_job_add_raw (ctx, + eh, + job_headers, + &handle_keyshare_lookup_finished, + kslo); + curl_slist_free_all (job_headers); + return kslo; +} + + +/* end of anastasis_api_keyshare_lookup.c */ diff --git a/src/restclient/anastasis_api_policy_lookup.c b/src/restclient/anastasis_api_policy_lookup.c new file mode 100644 index 0000000..1af95d7 --- /dev/null +++ b/src/restclient/anastasis_api_policy_lookup.c @@ -0,0 +1,356 @@ +/* + This file is part of ANASTASIS + Copyright (C) 2014-2019 GNUnet e.V. and INRIA + + ANASTASIS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1, + or (at your option) any later version. + + ANASTASIS 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with ANASTASIS; see the file COPYING.LGPL. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file lib/anastasis_api_policy_lookup.c + * @brief Implementation of the /policy GET and POST + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_signatures.h> + + +/** + * @brief A Contract Operation Handle + */ +struct ANASTASIS_PolicyLookupOperation +{ + + /** + * The url for this request, including parameters. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_PolicyLookupCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Public key of the account we are downloading from. + */ + struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; + + /** + * Signature returned in the #ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE + * header, or all zeros for none. + */ + struct ANASTASIS_AccountSignatureP account_sig; + + /** + * Version of the policy. + */ + unsigned int version; + +}; + + +void +ANASTASIS_policy_lookup_cancel (struct ANASTASIS_PolicyLookupOperation *plo) +{ + if (NULL != plo->job) + { + GNUNET_CURL_job_cancel (plo->job); + plo->job = NULL; + } + GNUNET_free (plo->url); + GNUNET_free (plo); +} + + +/** + * Process GET /policy response + */ +static void +handle_policy_lookup_finished (void *cls, + long response_code, + const void *data, + size_t data_size) +{ + struct ANASTASIS_PolicyLookupOperation *plo = cls; + + plo->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from GET /policy\n"); + break; + case MHD_HTTP_OK: + { + struct ANASTASIS_DownloadDetails dd; + struct ANASTASIS_UploadSignaturePS usp = { + .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD), + .purpose.size = htonl (sizeof (usp)), + }; + + GNUNET_CRYPTO_hash (data, + data_size, + &usp.new_recovery_data_hash); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD, + &usp, + &plo->account_sig.eddsa_sig, + &plo->account_pub.pub)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + /* Success, call callback with all details! */ + memset (&dd, 0, sizeof (dd)); + dd.sig = plo->account_sig; + dd.curr_policy_hash = usp.new_recovery_data_hash; + dd.policy = data; + dd.policy_size = data_size; + dd.version = plo->version; + plo->cb (plo->cb_cls, + response_code, + &dd); + plo->cb = NULL; + ANASTASIS_policy_lookup_cancel (plo); + return; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + 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", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + plo->cb (plo->cb_cls, + response_code, + NULL); + plo->cb = NULL; + ANASTASIS_policy_lookup_cancel (plo); +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_PolicyLookupOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_PolicyLookupOperation *plo = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "\n\r", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE)) + { + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + hdr_val, + strlen (hdr_val), + &plo->account_sig, + sizeof (struct ANASTASIS_AccountSignatureP))) + { + GNUNET_break_op (0); + GNUNET_free (ndup); + return 0; + } + } + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_POLICY_VERSION)) + { + char dummy; + + if (1 != + sscanf (hdr_val, + "%u%c", + &plo->version, + &dummy)) + { + GNUNET_break_op (0); + GNUNET_free (ndup); + return 0; + } + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_PolicyLookupOperation * +ANASTASIS_policy_lookup ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub, + ANASTASIS_PolicyLookupCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyLookupOperation *plo; + CURL *eh; + char *acc_pub_str; + char *path; + + GNUNET_assert (NULL != cb); + plo = GNUNET_new (struct ANASTASIS_PolicyLookupOperation); + plo->account_pub = *anastasis_pub; + acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub, + sizeof (*anastasis_pub)); + GNUNET_asprintf (&path, + "policy/%s", + acc_pub_str); + GNUNET_free (acc_pub_str); + plo->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (plo->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + plo)); + plo->cb = cb; + plo->cb_cls = cb_cls; + plo->job = GNUNET_CURL_job_add_raw (ctx, + eh, + GNUNET_NO, + &handle_policy_lookup_finished, + plo); + return plo; +} + + +struct ANASTASIS_PolicyLookupOperation * +ANASTASIS_policy_lookup_version ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub, + ANASTASIS_PolicyLookupCallback cb, + void *cb_cls, + unsigned int version) +{ + struct ANASTASIS_PolicyLookupOperation *plo; + CURL *eh; + char *acc_pub_str; + char *path; + char version_s[14]; + + GNUNET_assert (NULL != cb); + plo = GNUNET_new (struct ANASTASIS_PolicyLookupOperation); + plo->account_pub = *anastasis_pub; + acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub, + sizeof (*anastasis_pub)); + GNUNET_asprintf (&path, + "policy/%s", + acc_pub_str); + GNUNET_free (acc_pub_str); + GNUNET_snprintf (version_s, + sizeof (version_s), + "%u", + version); + plo->url = TALER_url_join (backend_url, + path, + "version", + version_s, + NULL); + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (plo->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + plo)); + plo->cb = cb; + plo->cb_cls = cb_cls; + plo->job = GNUNET_CURL_job_add_raw (ctx, + eh, + GNUNET_NO, + &handle_policy_lookup_finished, + plo); + return plo; +} diff --git a/src/restclient/anastasis_api_policy_store.c b/src/restclient/anastasis_api_policy_store.c new file mode 100644 index 0000000..7c6c244 --- /dev/null +++ b/src/restclient/anastasis_api_policy_store.c @@ -0,0 +1,527 @@ +/* + This file is part of ANASTASIS + Copyright (C) 2014-2021 Anastasis SARL + + ANASTASIS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1, + or (at your option) any later version. + + ANASTASIS 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with ANASTASIS; see the file COPYING.LGPL. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file lib/anastasis_api_policy_store.c + * @brief Implementation of the /policy GET and POST + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_signatures.h> +#include <taler/taler_merchant_service.h> +#include <taler/taler_json_lib.h> + + +struct ANASTASIS_PolicyStoreOperation +{ + /** + * Complete URL where the backend offers /policy + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * The CURL context to connect to the backend + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The callback to pass the backend response to + */ + ANASTASIS_PolicyStoreCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Payment URI we received from the service, or NULL. + */ + char *pay_uri; + + /** + * Policy version we received from the service, or NULL. + */ + char *policy_version; + + /** + * Policy expiration we received from the service, or NULL. + */ + char *policy_expiration; + + /** + * Copy of the uploaded data. Needed by curl. + */ + void *postcopy; + + /** + * Hash of the data we are uploading. + */ + struct GNUNET_HashCode new_upload_hash; +}; + + +void +ANASTASIS_policy_store_cancel ( + struct ANASTASIS_PolicyStoreOperation *pso) +{ + if (NULL != pso->job) + { + GNUNET_CURL_job_cancel (pso->job); + pso->job = NULL; + } + GNUNET_free (pso->policy_version); + GNUNET_free (pso->policy_expiration); + GNUNET_free (pso->pay_uri); + GNUNET_free (pso->url); + GNUNET_free (pso->postcopy); + GNUNET_free (pso); +} + + +/** + * Callback to process POST /policy response + * + * @param cls the `struct ANASTASIS_PolicyStoreOperation` + * @param response_code HTTP response code, 0 on error + * @param data response body + * @param data_size number of bytes in @a data + */ +static void +handle_policy_store_finished (void *cls, + long response_code, + const void *data, + size_t data_size) +{ + struct ANASTASIS_PolicyStoreOperation *pso = cls; + struct ANASTASIS_UploadDetails ud; + + pso->job = NULL; + memset (&ud, 0, sizeof (ud)); + ud.http_status = response_code; + ud.ec = TALER_EC_NONE; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Policy store finished with HTTP status %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + ud.us = ANASTASIS_US_SERVER_ERROR; + ud.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + case MHD_HTTP_NOT_MODIFIED: + { + unsigned long long version; + unsigned long long expiration; + char dummy; + + if (1 != sscanf (pso->policy_version, + "%llu%c", + &version, + &dummy)) + { + ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + ud.us = ANASTASIS_US_SERVER_ERROR; + break; + } + if (1 != sscanf (pso->policy_expiration, + "%llu%c", + &expiration, + &dummy)) + { + ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + ud.us = ANASTASIS_US_SERVER_ERROR; + break; + } + ud.us = ANASTASIS_US_SUCCESS; + ud.details.success.curr_backup_hash = &pso->new_upload_hash; + ud.details.success.policy_expiration + = GNUNET_TIME_absolute_add ( + GNUNET_TIME_UNIT_ZERO_ABS, + GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_SECONDS, + expiration)); + ud.details.success.policy_version = version; + } + break; + case MHD_HTTP_BAD_REQUEST: + GNUNET_break (0); + ud.us = ANASTASIS_US_CLIENT_ERROR; + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == pso->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (pso->pay_uri, + &pd)) ) + { + GNUNET_break_op (0); + ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST; + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Policy store operation requires payment `%s'\n", + pso->pay_uri); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &ud.details.payment.ps, + sizeof (ud.details.payment.ps))) + { + GNUNET_break (0); + ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + TALER_MERCHANT_parse_pay_uri_free (&pd); + } + ud.us = ANASTASIS_US_PAYMENT_REQUIRED; + ud.details.payment.payment_request = pso->pay_uri; + break; + case MHD_HTTP_PAYLOAD_TOO_LARGE: + ud.us = ANASTASIS_US_CLIENT_ERROR; + ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT; + break; + case MHD_HTTP_LENGTH_REQUIRED: + GNUNET_break (0); + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + ud.us = ANASTASIS_US_SERVER_ERROR; + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + ud.us = ANASTASIS_US_SERVER_ERROR; + break; + default: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + ud.us = ANASTASIS_US_SERVER_ERROR; + break; + } + pso->cb (pso->cb_cls, + &ud); + pso->cb = NULL; + ANASTASIS_policy_store_cancel (pso); +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_StorePolicyOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_PolicyStoreOperation *pso = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + "Taler")) + { + size_t len; + + /* found payment URI we care about! */ + GNUNET_free (pso->pay_uri); /* In case of duplicate header */ + pso->pay_uri = GNUNET_strdup (hdr_val); + len = strlen (pso->pay_uri); + while ( (len > 0) && + ( ('\n' == pso->pay_uri[len - 1]) || + ('\r' == pso->pay_uri[len - 1]) ) ) + { + len--; + pso->pay_uri[len] = '\0'; + } + } + + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_POLICY_VERSION)) + { + size_t len; + + /* found policy version we care about! */ + GNUNET_free (pso->policy_version); /* In case of duplicate header */ + pso->policy_version = GNUNET_strdup (hdr_val); + len = strlen (pso->policy_version); + while ( (len > 0) && + ( ('\n' == pso->policy_version[len - 1]) || + ('\r' == pso->policy_version[len - 1]) ) ) + { + len--; + pso->policy_version[len] = '\0'; + } + } + + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION)) + { + size_t len; + + /* found policy expiration we care about! */ + GNUNET_free (pso->policy_expiration); /* In case of duplicate header */ + pso->policy_expiration = GNUNET_strdup (hdr_val); + len = strlen (pso->policy_expiration); + while ( (len > 0) && + ( ('\n' == pso->policy_expiration[len - 1]) || + ('\r' == pso->policy_expiration[len - 1]) ) ) + { + len--; + pso->policy_expiration[len] = '\0'; + } + } + + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_PolicyStoreOperation * +ANASTASIS_policy_store ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv, + const void *recovery_data, + size_t recovery_data_size, + uint32_t payment_years_requested, + const struct ANASTASIS_PaymentSecretP *payment_secret, + struct GNUNET_TIME_Relative payment_timeout, + ANASTASIS_PolicyStoreCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyStoreOperation *pso; + struct ANASTASIS_AccountSignatureP account_sig; + unsigned long long tms; + CURL *eh; + struct curl_slist *job_headers; + struct ANASTASIS_UploadSignaturePS usp = { + .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD), + .purpose.size = htonl (sizeof (usp)) + }; + + tms = (unsigned long long) (payment_timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + GNUNET_CRYPTO_hash (recovery_data, + recovery_data_size, + &usp.new_recovery_data_hash); + GNUNET_CRYPTO_eddsa_sign (&anastasis_priv->priv, + &usp, + &account_sig.eddsa_sig); + /* setup our HTTP headers */ + job_headers = NULL; + { + struct curl_slist *ext; + char *val; + char *hdr; + + /* Set Anastasis-Policy-Signature header */ + val = GNUNET_STRINGS_data_to_string_alloc (&account_sig, + sizeof (account_sig)); + GNUNET_asprintf (&hdr, + "%s: %s", + ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE, + val); + GNUNET_free (val); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + + /* set Etag header */ + val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash, + sizeof (struct GNUNET_HashCode)); + GNUNET_asprintf (&hdr, + "%s: %s", + MHD_HTTP_HEADER_IF_NONE_MATCH, + val); + GNUNET_free (val); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + + /* Setup Payment-Identifier header */ + if (NULL != payment_secret) + { + char *paid_order_id; + + paid_order_id = GNUNET_STRINGS_data_to_string_alloc ( + payment_secret, + sizeof (*payment_secret)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Beginning policy store operation with payment secret `%s'\n", + paid_order_id); + GNUNET_asprintf (&hdr, + "%s: %s", + ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER, + paid_order_id); + GNUNET_free (paid_order_id); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Beginning policy store operation without payment secret\n"); + } + } + /* Finished setting up headers */ + pso = GNUNET_new (struct ANASTASIS_PolicyStoreOperation); + pso->postcopy = GNUNET_memdup (recovery_data, + recovery_data_size); + pso->new_upload_hash = usp.new_recovery_data_hash; + { + char *acc_pub_str; + char *path; + struct ANASTASIS_CRYPTO_AccountPublicKeyP pub; + char timeout_ms[32]; + char pyrs[32]; + + GNUNET_snprintf (timeout_ms, + sizeof (timeout_ms), + "%llu", + tms); + GNUNET_snprintf (pyrs, + sizeof (pyrs), + "%u", + (unsigned int) payment_years_requested); + GNUNET_CRYPTO_eddsa_key_get_public (&anastasis_priv->priv, + &pub.pub); + acc_pub_str + = GNUNET_STRINGS_data_to_string_alloc (&pub, + sizeof (pub)); + GNUNET_asprintf (&path, + "policy/%s", + acc_pub_str); + GNUNET_free (acc_pub_str); + pso->url = TALER_url_join (backend_url, + path, + "storage_duration", + (0 != payment_years_requested) + ? pyrs + : NULL, + "timeout_ms", + (0 != payment_timeout.rel_value_us) + ? timeout_ms + : NULL, + NULL); + GNUNET_free (path); + } + pso->ctx = ctx; + pso->cb = cb; + pso->cb_cls = cb_cls; + eh = ANASTASIS_curl_easy_get_ (pso->url); + if (0 != tms) + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 5000))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + pso->postcopy)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + (long) recovery_data_size)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + pso)); + pso->job = GNUNET_CURL_job_add_raw (ctx, + eh, + job_headers, + &handle_policy_store_finished, + pso); + curl_slist_free_all (job_headers); + return pso; +} diff --git a/src/restclient/anastasis_api_truth_store.c b/src/restclient/anastasis_api_truth_store.c new file mode 100644 index 0000000..ebd7d10 --- /dev/null +++ b/src/restclient/anastasis_api_truth_store.c @@ -0,0 +1,354 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021 Taler Systems SA + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/anastasis_api_truth_store.c + * @brief Implementation of the /truth GET and POST + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_merchant_service.h> + + +struct ANASTASIS_TruthStoreOperation +{ + /** + * Complete URL where the backend offers /truth + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * The CURL context to connect to the backend + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The callback to pass the backend response to + */ + ANASTASIS_TruthStoreCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Reference to data (for cleanup). + */ + char *data; + + /** + * Payment URI we received from the service, or NULL. + */ + char *pay_uri; +}; + + +void +ANASTASIS_truth_store_cancel ( + struct ANASTASIS_TruthStoreOperation *tso) +{ + if (NULL != tso->job) + { + GNUNET_CURL_job_cancel (tso->job); + tso->job = NULL; + } + GNUNET_free (tso->pay_uri); + GNUNET_free (tso->url); + GNUNET_free (tso->data); + GNUNET_free (tso); +} + + +/** + * Callback to process POST /truth response + * + * @param cls the `struct ANASTASIS_TruthStoreOperation` + * @param response_code HTTP response code, 0 on error + * @param data + * @param data_size + */ +static void +handle_truth_store_finished (void *cls, + long response_code, + const void *data, + size_t data_size) +{ + struct ANASTASIS_TruthStoreOperation *tso = cls; + struct ANASTASIS_UploadDetails ud; + + tso->job = NULL; + memset (&ud, 0, sizeof (ud)); + ud.http_status = response_code; + ud.ec = TALER_EC_NONE; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_NO_CONTENT: + ud.us = ANASTASIS_US_SUCCESS; + break; + case MHD_HTTP_NOT_MODIFIED: + ud.us = ANASTASIS_US_SUCCESS; + break; + case MHD_HTTP_BAD_REQUEST: + GNUNET_break (0); + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == tso->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (tso->pay_uri, + &pd)) ) + { + GNUNET_break_op (0); + ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST; + break; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &ud.details.payment.ps, + sizeof (ud.details.payment.ps))) + { + GNUNET_break (0); + ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + TALER_MERCHANT_parse_pay_uri_free (&pd); + } + ud.us = ANASTASIS_US_PAYMENT_REQUIRED; + ud.details.payment.payment_request = tso->pay_uri; + break; + case MHD_HTTP_CONFLICT: + ud.us = ANASTASIS_US_CONFLICTING_TRUTH; + break; + case MHD_HTTP_LENGTH_REQUIRED: + GNUNET_break (0); + break; + case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_TOO_MANY_REQUESTS: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + default: + GNUNET_break (0); + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + } + tso->cb (tso->cb_cls, + &ud); + tso->cb = NULL; + ANASTASIS_truth_store_cancel (tso); +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_StorePolicyOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_TruthStoreOperation *tso = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_TALER)) + { + size_t len; + + /* found payment URI we care about! */ + tso->pay_uri = GNUNET_strdup (hdr_val); + len = strlen (tso->pay_uri); + while ( (len > 0) && + ( ('\n' == tso->pay_uri[len - 1]) || + ('\r' == tso->pay_uri[len - 1]) ) ) + { + len--; + tso->pay_uri[len] = '\0'; + } + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_TruthStoreOperation * +ANASTASIS_truth_store ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, + const char *type, + const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *encrypted_keyshare, + const char *truth_mime, + size_t encrypted_truth_size, + const void *encrypted_truth, + uint32_t payment_years_requested, + struct GNUNET_TIME_Relative payment_timeout, + ANASTASIS_TruthStoreCallback cb, + void *cb_cls) +{ + struct ANASTASIS_TruthStoreOperation *tso; + CURL *eh; + char *json_str; + unsigned long long tms; + + tms = (unsigned long long) (payment_timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + tso = GNUNET_new (struct ANASTASIS_TruthStoreOperation); + { + char *uuid_str; + char *path; + char timeout_ms[32]; + + GNUNET_snprintf (timeout_ms, + sizeof (timeout_ms), + "%llu", + tms); + uuid_str = GNUNET_STRINGS_data_to_string_alloc (uuid, + sizeof (*uuid)); + GNUNET_asprintf (&path, + "truth/%s", + uuid_str); + tso->url = TALER_url_join (backend_url, + path, + "timeout_ms", + (0 != payment_timeout.rel_value_us) + ? timeout_ms + : NULL, + NULL); + GNUNET_free (path); + GNUNET_free (uuid_str); + } + { + json_t *truth_data; + + truth_data = json_pack ("{s:o," /* encrypted KeyShare */ + " s:s," /* type */ + " s:o," /* nonce */ + " s:s," /* truth_mime */ + " s:I}", /* payment years */ + "keyshare_data", + GNUNET_JSON_from_data_auto (encrypted_keyshare), + "type", + type, + "encrypted_truth", + GNUNET_JSON_from_data (encrypted_truth, + encrypted_truth_size), + "truth_mime", + (NULL != truth_mime) + ? truth_mime + : "", + "storage_duration_years", + (json_int_t) payment_years_requested); + GNUNET_assert (NULL != truth_data); + json_str = json_dumps (truth_data, + JSON_COMPACT); + GNUNET_assert (NULL != json_str); + json_decref (truth_data); + } + tso->ctx = ctx; + tso->data = json_str; + tso->cb = cb; + tso->cb_cls = cb_cls; + eh = ANASTASIS_curl_easy_get_ (tso->url); + if (0 != tms) + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 5000))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + json_str)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (json_str))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + tso)); + tso->job = GNUNET_CURL_job_add_raw (ctx, + eh, + GNUNET_NO, + &handle_truth_store_finished, + tso); + return tso; +} |