/* This file is part of TALER Copyright (C) 2014-2021 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 */ /** * @file lib/exchange_api_handle.c * @brief Implementation of the "handle" component of the exchange's HTTP API * @author Sree Harsha Totakura * @author Christian Grothoff */ #include "platform.h" #include #include #include "taler_json_lib.h" #include "taler_exchange_service.h" #include "taler_auditor_service.h" #include "taler_signatures.h" #include "taler_extensions.h" #include "exchange_api_handle.h" #include "exchange_api_curl_defaults.h" #include "backoff.h" #include "taler_curl_lib.h" /** * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ #define EXCHANGE_PROTOCOL_CURRENT 12 /** * How many versions are we backwards compatible with? */ #define EXCHANGE_PROTOCOL_AGE 0 /** * Current version for (local) JSON serialization of persisted * /keys data. */ #define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0 /** * How far off do we allow key liftimes to be? */ #define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS /** * If the "Expire" cache control header is missing, for * how long do we assume the reply to be valid at least? */ #define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS /** * Set to 1 for extra debug logging. */ #define DEBUG 0 /** * Log error related to CURL operations. * * @param type log level * @param function which function failed to run * @param code what was the curl error code */ #define CURL_STRERROR(type, function, code) \ GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ function, __FILE__, __LINE__, curl_easy_strerror (code)); /** * Data for the request to get the /keys of a exchange. */ struct KeysRequest; /** * Entry in DLL of auditors used by an exchange. */ struct TEAH_AuditorListEntry { /** * Next pointer of DLL. */ struct TEAH_AuditorListEntry *next; /** * Prev pointer of DLL. */ struct TEAH_AuditorListEntry *prev; /** * Base URL of the auditor. */ char *auditor_url; /** * Handle to the auditor. */ struct TALER_AUDITOR_Handle *ah; /** * Head of DLL of interactions with this auditor. */ struct TEAH_AuditorInteractionEntry *ai_head; /** * Tail of DLL of interactions with this auditor. */ struct TEAH_AuditorInteractionEntry *ai_tail; /** * Public key of the auditor. */ struct TALER_AuditorPublicKeyP auditor_pub; /** * Flag indicating that the auditor is available and that protocol * version compatibility is given. */ int is_up; }; /* ***************** Internal /keys fetching ************* */ /** * Data for the request to get the /keys of a exchange. */ struct KeysRequest { /** * The connection to exchange this request handle will use */ struct TALER_EXCHANGE_Handle *exchange; /** * The url for this handle */ char *url; /** * Entry for this request with the `struct GNUNET_CURL_Context`. */ struct GNUNET_CURL_Job *job; /** * Expiration time according to "Expire:" header. * 0 if not provided by the server. */ struct GNUNET_TIME_Timestamp expire; }; void TEAH_acc_confirmation_cb (void *cls, const struct TALER_AUDITOR_HttpResponse *hr) { struct TEAH_AuditorInteractionEntry *aie = cls; struct TEAH_AuditorListEntry *ale = aie->ale; if (MHD_HTTP_OK != hr->http_status) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n", ale->auditor_url, hr->http_status, hr->ec); } GNUNET_CONTAINER_DLL_remove (ale->ai_head, ale->ai_tail, aie); GNUNET_free (aie); } void TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, TEAH_AuditorCallback ac, void *ac_cls) { if (NULL == h->auditors_head) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No auditor available for exchange `%s'. Not submitting deposit confirmations.\n", h->url); return; } for (struct TEAH_AuditorListEntry *ale = h->auditors_head; NULL != ale; ale = ale->next) { struct TEAH_AuditorInteractionEntry *aie; if (GNUNET_NO == ale->is_up) continue; aie = ac (ac_cls, ale->ah, &ale->auditor_pub); if (NULL != aie) { aie->ale = ale; GNUNET_CONTAINER_DLL_insert (ale->ai_head, ale->ai_tail, aie); } } } /** * Release memory occupied by a keys request. Note that this does not * cancel the request itself. * * @param kr request to free */ static void free_keys_request (struct KeysRequest *kr) { GNUNET_free (kr->url); GNUNET_free (kr); } #define EXITIF(cond) \ do { \ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ } while (0) /** * Parse a exchange's signing key encoded in JSON. * * @param[out] sign_key where to return the result * @param check_sigs should we check signatures? * @param[in] sign_key_obj json to parse * @param master_key master key to use to verify signature * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ static enum GNUNET_GenericReturnValue parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, int check_sigs, json_t *sign_key_obj, const struct TALER_MasterPublicKeyP *master_key) { struct TALER_MasterSignatureP sign_key_issue_sig; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig", &sign_key_issue_sig), GNUNET_JSON_spec_fixed_auto ("key", &sign_key->key), GNUNET_JSON_spec_timestamp ("stamp_start", &sign_key->valid_from), GNUNET_JSON_spec_timestamp ("stamp_expire", &sign_key->valid_until), GNUNET_JSON_spec_timestamp ("stamp_end", &sign_key->valid_legal), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (sign_key_obj, spec, NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } if (! check_sigs) return GNUNET_OK; if (GNUNET_OK != TALER_exchange_offline_signkey_validity_verify ( &sign_key->key, sign_key->valid_from, sign_key->valid_until, sign_key->valid_legal, master_key, &sign_key_issue_sig)) { GNUNET_break_op (0); return GNUNET_SYSERR; } sign_key->master_sig = sign_key_issue_sig; return GNUNET_OK; } /** * Parse a exchange's denomination key encoded in JSON. * * @param[out] denom_key where to return the result * @param check_sigs should we check signatures? * @param[in] denom_key_obj json to parse * @param master_key master key to use to verify signature * @param hash_context where to accumulate data for signature verification * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ static enum GNUNET_GenericReturnValue parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, int check_sigs, json_t *denom_key_obj, struct TALER_MasterPublicKeyP *master_key, struct GNUNET_HashContext *hash_context) { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig", &denom_key->master_sig), GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", &denom_key->expire_deposit), GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", &denom_key->withdraw_valid_until), GNUNET_JSON_spec_timestamp ("stamp_start", &denom_key->valid_from), GNUNET_JSON_spec_timestamp ("stamp_expire_legal", &denom_key->expire_legal), TALER_JSON_spec_amount_any ("value", &denom_key->value), TALER_JSON_spec_amount_any ("fee_withdraw", &denom_key->fee_withdraw), TALER_JSON_spec_amount_any ("fee_deposit", &denom_key->fee_deposit), TALER_JSON_spec_amount_any ("fee_refresh", &denom_key->fee_refresh), TALER_JSON_spec_amount_any ("fee_refund", &denom_key->fee_refund), TALER_JSON_spec_denom_pub ("denom_pub", &denom_key->key), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (denom_key_obj, spec, NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } TALER_denom_pub_hash (&denom_key->key, &denom_key->h_key); if (NULL != hash_context) GNUNET_CRYPTO_hash_context_read (hash_context, &denom_key->h_key, sizeof (struct GNUNET_HashCode)); if (! check_sigs) return GNUNET_OK; EXITIF (GNUNET_SYSERR == TALER_exchange_offline_denom_validity_verify ( &denom_key->h_key, denom_key->valid_from, denom_key->withdraw_valid_until, denom_key->expire_deposit, denom_key->expire_legal, &denom_key->value, &denom_key->fee_withdraw, &denom_key->fee_deposit, &denom_key->fee_refresh, &denom_key->fee_refund, master_key, &denom_key->master_sig)); return GNUNET_OK; EXITIF_exit: /* invalidate denom_key, just to be sure */ memset (denom_key, 0, sizeof (*denom_key)); GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } /** * Parse a exchange's auditor information encoded in JSON. * * @param[out] auditor where to return the result * @param check_sigs should we check signatures * @param[in] auditor_obj json to parse * @param key_data information about denomination keys * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ static enum GNUNET_GenericReturnValue parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, int check_sigs, json_t *auditor_obj, const struct TALER_EXCHANGE_Keys *key_data) { json_t *keys; json_t *key; unsigned int len; unsigned int off; unsigned int i; const char *auditor_url; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("auditor_pub", &auditor->auditor_pub), GNUNET_JSON_spec_string ("auditor_url", &auditor_url), GNUNET_JSON_spec_json ("denomination_keys", &keys), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (auditor_obj, spec, NULL, NULL)) { GNUNET_break_op (0); #if DEBUG json_dumpf (auditor_obj, stderr, JSON_INDENT (2)); #endif return GNUNET_SYSERR; } auditor->auditor_url = GNUNET_strdup (auditor_url); len = json_array_size (keys); auditor->denom_keys = GNUNET_new_array (len, struct TALER_EXCHANGE_AuditorDenominationInfo); off = 0; json_array_foreach (keys, i, key) { struct TALER_AuditorSignatureP auditor_sig; struct TALER_DenominationHash denom_h; const struct TALER_EXCHANGE_DenomPublicKey *dk; unsigned int dk_off; struct GNUNET_JSON_Specification kspec[] = { GNUNET_JSON_spec_fixed_auto ("auditor_sig", &auditor_sig), GNUNET_JSON_spec_fixed_auto ("denom_pub_h", &denom_h), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (key, kspec, NULL, NULL)) { GNUNET_break_op (0); continue; } dk = NULL; dk_off = UINT_MAX; for (unsigned int j = 0; jnum_denom_keys; j++) { if (0 == GNUNET_memcmp (&denom_h, &key_data->denom_keys[j].h_key)) { dk = &key_data->denom_keys[j]; dk_off = j; break; } } if (NULL == dk) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Auditor signed denomination %s, which we do not know. Ignoring signature.\n", GNUNET_h2s (&denom_h.hash)); continue; } if (check_sigs) { if (GNUNET_OK != TALER_auditor_denom_validity_verify ( auditor_url, &dk->h_key, &key_data->master_pub, dk->valid_from, dk->withdraw_valid_until, dk->expire_deposit, dk->expire_legal, &dk->value, &dk->fee_withdraw, &dk->fee_deposit, &dk->fee_refresh, &dk->fee_refund, &auditor->auditor_pub, &auditor_sig)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } } auditor->denom_keys[off].denom_key_offset = dk_off; auditor->denom_keys[off].auditor_sig = auditor_sig; off++; } auditor->num_denom_keys = off; GNUNET_JSON_parse_free (spec); return GNUNET_OK; } /** * Function called with information about the auditor. Marks an * auditor as 'up'. * * @param cls closure, a `struct TEAH_AuditorListEntry *` * @param hr http response from the auditor * @param vi basic information about the auditor * @param compat protocol compatibility information */ static void auditor_version_cb ( void *cls, const struct TALER_AUDITOR_HttpResponse *hr, const struct TALER_AUDITOR_VersionInformation *vi, enum TALER_AUDITOR_VersionCompatibility compat) { struct TEAH_AuditorListEntry *ale = cls; (void) hr; if (NULL == vi) { /* In this case, we don't mark the auditor as 'up' */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Auditor `%s' gave unexpected version response.\n", ale->auditor_url); return; } if (0 != (TALER_AUDITOR_VC_INCOMPATIBLE & compat)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Auditor `%s' runs incompatible protocol version!\n", ale->auditor_url); if (0 != (TALER_AUDITOR_VC_OLDER & compat)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Auditor `%s' runs outdated protocol version!\n", ale->auditor_url); } if (0 != (TALER_AUDITOR_VC_NEWER & compat)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Auditor `%s' runs more recent incompatible version. We should upgrade!\n", ale->auditor_url); } return; } ale->is_up = GNUNET_YES; } /** * Recalculate our auditor list, we got /keys and it may have * changed. * * @param exchange exchange for which to update the list. */ static void update_auditors (struct TALER_EXCHANGE_Handle *exchange) { struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; TALER_LOG_DEBUG ("Updating auditors\n"); for (unsigned int i = 0; inum_auditors; i++) { /* Compare auditor data from /keys with auditor data * from owned exchange structures. */ struct TALER_EXCHANGE_AuditorInformation *auditor = &kd->auditors[i]; struct TEAH_AuditorListEntry *ale = NULL; for (struct TEAH_AuditorListEntry *a = exchange->auditors_head; NULL != a; a = a->next) { if (0 == GNUNET_memcmp (&auditor->auditor_pub, &a->auditor_pub)) { ale = a; break; } } if (NULL != ale) continue; /* found, no need to add */ /* new auditor, add */ TALER_LOG_DEBUG ("Found new auditor!\n"); ale = GNUNET_new (struct TEAH_AuditorListEntry); ale->auditor_pub = auditor->auditor_pub; ale->auditor_url = GNUNET_strdup (auditor->auditor_url); GNUNET_CONTAINER_DLL_insert (exchange->auditors_head, exchange->auditors_tail, ale); ale->ah = TALER_AUDITOR_connect (exchange->ctx, ale->auditor_url, &auditor_version_cb, ale); } } /** * Compare two denomination keys. Ignores revocation data. * * @param denom1 first denomination key * @param denom2 second denomination key * @return 0 if the two keys are equal (not necessarily * the same object), 1 otherwise. */ static unsigned int denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1, const struct TALER_EXCHANGE_DenomPublicKey *denom2) { struct TALER_EXCHANGE_DenomPublicKey tmp1; struct TALER_EXCHANGE_DenomPublicKey tmp2; if (0 != TALER_denom_pub_cmp (&denom1->key, &denom2->key)) return 1; tmp1 = *denom1; tmp2 = *denom2; tmp1.revoked = false; tmp2.revoked = false; memset (&tmp1.key, 0, sizeof (tmp1.key)); memset (&tmp2.key, 0, sizeof (tmp2.key)); return GNUNET_memcmp (&tmp1, &tmp2); } /** * Decode the JSON in @a resp_obj from the /keys response * and store the data in the @a key_data. * * @param[in] resp_obj JSON object to parse * @param check_sig true if we should check the signature * @param[out] key_data where to store the results we decoded * @param[out] vc where to store version compatibility data * @return #GNUNET_OK on success, #GNUNET_SYSERR on error * (malformed JSON) */ static enum GNUNET_GenericReturnValue decode_keys_json (const json_t *resp_obj, bool check_sig, struct TALER_EXCHANGE_Keys *key_data, enum TALER_EXCHANGE_VersionCompatibility *vc) { struct TALER_ExchangeSignatureP sig; struct GNUNET_HashContext *hash_context; struct TALER_ExchangePublicKeyP pub; const char *currency; struct GNUNET_JSON_Specification mspec[] = { GNUNET_JSON_spec_fixed_auto ("eddsa_sig", &sig), GNUNET_JSON_spec_fixed_auto ("eddsa_pub", &pub), GNUNET_JSON_spec_fixed_auto ("master_public_key", &key_data->master_pub), GNUNET_JSON_spec_timestamp ("list_issue_date", &key_data->list_issue_date), GNUNET_JSON_spec_relative_time ("reserve_closing_delay", &key_data->reserve_closing_delay), GNUNET_JSON_spec_string ("currency", ¤cy), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("wallet_balance_limit_without_kyc", &key_data->wallet_balance_limit_without_kyc)), GNUNET_JSON_spec_end () }; if (JSON_OBJECT != json_typeof (resp_obj)) { GNUNET_break_op (0); return GNUNET_SYSERR; } #if DEBUG json_dumpf (resp_obj, stderr, JSON_INDENT (2)); #endif /* check the version */ { const char *ver; unsigned int age; unsigned int revision; unsigned int current; char dummy; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("version", &ver), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (resp_obj, spec, NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } if (3 != sscanf (ver, "%u:%u:%u%c", ¤t, &revision, &age, &dummy)) { GNUNET_break_op (0); return GNUNET_SYSERR; } *vc = TALER_EXCHANGE_VC_MATCH; if (EXCHANGE_PROTOCOL_CURRENT < current) { *vc |= TALER_EXCHANGE_VC_NEWER; if (EXCHANGE_PROTOCOL_CURRENT < current - age) *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; } if (EXCHANGE_PROTOCOL_CURRENT > current) { *vc |= TALER_EXCHANGE_VC_OLDER; if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > current) *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; } key_data->version = GNUNET_strdup (ver); } hash_context = NULL; EXITIF (GNUNET_OK != GNUNET_JSON_parse (resp_obj, (check_sig) ? mspec : &mspec[2], NULL, NULL)); key_data->currency = GNUNET_strdup (currency); if (GNUNET_OK == TALER_amount_is_valid (&key_data->wallet_balance_limit_without_kyc)) { if (0 != strcasecmp (currency, key_data->wallet_balance_limit_without_kyc.currency)) { GNUNET_break_op (0); return GNUNET_SYSERR; } } /* parse the master public key and issue date of the response */ if (check_sig) hash_context = GNUNET_CRYPTO_hash_context_start (); /* parse the signing keys */ { json_t *sign_keys_array; json_t *sign_key_obj; unsigned int index; EXITIF (NULL == (sign_keys_array = json_object_get (resp_obj, "signkeys"))); EXITIF (! json_is_array (sign_keys_array)); if (0 != (key_data->num_sign_keys = json_array_size (sign_keys_array))) { key_data->sign_keys = GNUNET_new_array (key_data->num_sign_keys, struct TALER_EXCHANGE_SigningPublicKey); json_array_foreach (sign_keys_array, index, sign_key_obj) { EXITIF (GNUNET_SYSERR == parse_json_signkey (&key_data->sign_keys[index], check_sig, sign_key_obj, &key_data->master_pub)); } } } /* Parse the supported extension(s): age-restriction. */ /* TODO: maybe lift this into a FP in TALER_Extension ? */ { json_t *age_restriction = json_object_get (resp_obj, "age_restriction"); if (NULL != age_restriction) { bool critical; const char *version; const char *age_groups; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_bool ("critical", &critical), GNUNET_JSON_spec_string ("version", &version), GNUNET_JSON_spec_string ("age_groups", &age_groups), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (age_restriction, spec, NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } if (critical || // do we care? 0 != strncmp (version, "1", 1) ) /* TODO: better compatibility check */ { GNUNET_break_op (0); return GNUNET_SYSERR; } if (GNUNET_OK != TALER_parse_age_group_string (age_groups, &key_data->age_mask)) { // TODO: print more specific error? GNUNET_break_op (0); return GNUNET_SYSERR; } } } /* parse the denomination keys, merging with the possibly EXISTING array as required (/keys cherry picking) */ { /* The denominations can be in "denoms" and (optionally) in * "age_restricted_denoms" */ struct { char *name; bool is_optional_age_restriction;} hive[2] = { { "denoms", false }, { "age_restricted_denoms", true }, }; for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++) { json_t *denom_keys_array; json_t *denom_key_obj; unsigned int index; denom_keys_array = json_object_get (resp_obj, hive[s].name); EXITIF (NULL == denom_keys_array && ! hive[s].is_optional_age_restriction); if (NULL == denom_keys_array && hive[s].is_optional_age_restriction) continue; /* if "age_restricted_denoms" exists, age-restriction better be enabled * (that is: mask non-zero) */ EXITIF (NULL != denom_keys_array && hive[s].is_optional_age_restriction && 0 == key_data->age_mask.mask); EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); json_array_foreach (denom_keys_array, index, denom_key_obj) { struct TALER_EXCHANGE_DenomPublicKey dk; bool found = false; memset (&dk, 0, sizeof (dk)); EXITIF (GNUNET_SYSERR == parse_json_denomkey (&dk, check_sig, denom_key_obj, &key_data->master_pub, hash_context)); /* Mark age restriction according where we got this denomination from, * "denoms" or "age_restricted_denoms" */ if (hive[s].is_optional_age_restriction) dk.age_restricted = true; for (unsigned int j = 0; jnum_denom_keys; j++) { if (0 == denoms_cmp (&dk, &key_data->denom_keys[j])) { found = true; break; } } if (found) { /* 0:0:0 did not support /keys cherry picking */ TALER_LOG_DEBUG ("Skipping denomination key: already know it\n"); TALER_denom_pub_free (&dk.key); continue; } if (key_data->denom_keys_size == key_data->num_denom_keys) GNUNET_array_grow (key_data->denom_keys, key_data->denom_keys_size, key_data->denom_keys_size * 2 + 2); key_data->denom_keys[key_data->num_denom_keys++] = dk; /* Update "last_denom_issue_date" */ TALER_LOG_DEBUG ("Adding denomination key that is valid_until %s\n", GNUNET_TIME_timestamp2s (dk.valid_from)); key_data->last_denom_issue_date = GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date, dk.valid_from); }; } } /* parse the auditor information */ { json_t *auditors_array; json_t *auditor_info; unsigned int index; EXITIF (NULL == (auditors_array = json_object_get (resp_obj, "auditors"))); EXITIF (JSON_ARRAY != json_typeof (auditors_array)); /* Merge with the existing auditor information we have (/keys cherry picking) */ json_array_foreach (auditors_array, index, auditor_info) { struct TALER_EXCHANGE_AuditorInformation ai; bool found = false; memset (&ai, 0, sizeof (ai)); EXITIF (GNUNET_SYSERR == parse_json_auditor (&ai, check_sig, auditor_info, key_data)); for (unsigned int j = 0; jnum_auditors; j++) { struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j]; if (0 == GNUNET_memcmp (&ai.auditor_pub, &aix->auditor_pub)) { found = true; /* Merge denomination key signatures of downloaded /keys into existing auditor information 'aix'. */ TALER_LOG_DEBUG ( "Merging %u new audited keys with %u known audited keys\n", aix->num_denom_keys, ai.num_denom_keys); for (unsigned int i = 0; inum_denom_keys; k++) { if (aix->denom_keys[k].denom_key_offset == ai.denom_keys[i].denom_key_offset) { kfound = true; break; } } if (! kfound) GNUNET_array_append (aix->denom_keys, aix->num_denom_keys, ai.denom_keys[i]); } break; } } if (found) { GNUNET_array_grow (ai.denom_keys, ai.num_denom_keys, 0); GNUNET_free (ai.auditor_url); continue; /* we are done */ } if (key_data->auditors_size == key_data->num_auditors) GNUNET_array_grow (key_data->auditors, key_data->auditors_size, key_data->auditors_size * 2 + 2); GNUNET_assert (NULL != ai.auditor_url); key_data->auditors[key_data->num_auditors++] = ai; }; } /* parse the revocation/recoup information */ { json_t *recoup_array; json_t *recoup_info; unsigned int index; if (NULL != (recoup_array = json_object_get (resp_obj, "recoup"))) { EXITIF (JSON_ARRAY != json_typeof (recoup_array)); json_array_foreach (recoup_array, index, recoup_info) { struct TALER_DenominationHash h_denom_pub; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("h_denom_pub", &h_denom_pub), GNUNET_JSON_spec_end () }; EXITIF (GNUNET_OK != GNUNET_JSON_parse (recoup_info, spec, NULL, NULL)); for (unsigned int j = 0; jnum_denom_keys; j++) { if (0 == GNUNET_memcmp (&h_denom_pub, &key_data->denom_keys[j].h_key)) { key_data->denom_keys[j].revoked = GNUNET_YES; break; } } }; } } if (check_sig) { struct TALER_ExchangeKeySetPS ks = { .purpose.size = htonl (sizeof (ks)), .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET), .list_issue_date = GNUNET_TIME_timestamp_hton (key_data->list_issue_date) }; GNUNET_CRYPTO_hash_context_finish (hash_context, &ks.hc); hash_context = NULL; EXITIF (GNUNET_OK != TALER_EXCHANGE_test_signing_key (key_data, &pub)); EXITIF (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, &ks, &sig.eddsa_signature, &pub.eddsa_pub)); } return GNUNET_OK; EXITIF_exit: *vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; if (NULL != hash_context) GNUNET_CRYPTO_hash_context_abort (hash_context); return GNUNET_SYSERR; } /** * Free key data object. * * @param key_data data to free (pointer itself excluded) */ static void free_key_data (struct TALER_EXCHANGE_Keys *key_data) { GNUNET_array_grow (key_data->sign_keys, key_data->num_sign_keys, 0); for (unsigned int i = 0; inum_denom_keys; i++) TALER_denom_pub_free (&key_data->denom_keys[i].key); GNUNET_array_grow (key_data->denom_keys, key_data->denom_keys_size, 0); for (unsigned int i = 0; inum_auditors; i++) { GNUNET_array_grow (key_data->auditors[i].denom_keys, key_data->auditors[i].num_denom_keys, 0); GNUNET_free (key_data->auditors[i].auditor_url); } GNUNET_array_grow (key_data->auditors, key_data->auditors_size, 0); GNUNET_free (key_data->version); key_data->version = NULL; GNUNET_free (key_data->currency); key_data->currency = NULL; } /** * Initiate download of /keys from the exchange. * * @param cls exchange where to download /keys from */ static void request_keys (void *cls); void TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange, struct GNUNET_TIME_Timestamp last_denom_new) { TALER_LOG_DEBUG ( "Application explicitly set last denomination validity to %s\n", GNUNET_TIME_timestamp2s (last_denom_new)); exchange->key_data.last_denom_issue_date = last_denom_new; } struct GNUNET_TIME_Timestamp TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, enum TALER_EXCHANGE_CheckKeysFlags flags) { bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD); bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS); if (NULL != exchange->kr) return GNUNET_TIME_UNIT_ZERO_TS; if (pull_all_keys) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Forcing re-download of all exchange keys\n"); GNUNET_break (GNUNET_YES == force_download); exchange->state = MHS_INIT; } if ( (! force_download) && (GNUNET_TIME_absolute_is_future ( exchange->key_data_expiration.abs_time)) ) return exchange->key_data_expiration; if (NULL == exchange->retry_task) exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, exchange); return GNUNET_TIME_UNIT_ZERO_TS; } /** * Callback used when downloading the reply to a /keys request * is complete. * * @param cls the `struct KeysRequest` * @param response_code HTTP response code, 0 on error * @param resp_obj parsed JSON result, NULL on error */ static void keys_completed_cb (void *cls, long response_code, const void *resp_obj) { struct KeysRequest *kr = cls; struct TALER_EXCHANGE_Handle *exchange = kr->exchange; struct TALER_EXCHANGE_Keys kd; struct TALER_EXCHANGE_Keys kd_old; enum TALER_EXCHANGE_VersionCompatibility vc; const json_t *j = resp_obj; struct TALER_EXCHANGE_HttpResponse hr = { .reply = j, .http_status = (unsigned int) response_code }; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received keys from URL `%s' with status %ld and expiration %s.\n", kr->url, response_code, GNUNET_TIME_timestamp2s (kr->expire)); if (GNUNET_TIME_absolute_is_past (kr->expire.abs_time)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Exchange failed to give expiration time, assuming in %s\n", GNUNET_TIME_relative2s (DEFAULT_EXPIRATION, true)); kr->expire = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION)); } kd_old = exchange->key_data; memset (&kd, 0, sizeof (struct TALER_EXCHANGE_Keys)); vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; switch (response_code) { case 0: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to receive /keys response from exchange %s\n", exchange->url); free_keys_request (kr); exchange->keys_error_count++; exchange->kr = NULL; GNUNET_assert (NULL == exchange->retry_task); exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, &request_keys, exchange); return; case MHD_HTTP_OK: exchange->keys_error_count = 0; if (NULL == j) { response_code = 0; break; } /* We keep the denomination keys and auditor signatures from the previous iteration (/keys cherry picking) */ kd.num_denom_keys = kd_old.num_denom_keys; kd.last_denom_issue_date = kd_old.last_denom_issue_date; GNUNET_array_grow (kd.denom_keys, kd.denom_keys_size, kd.num_denom_keys); /* First make a shallow copy, we then need another pass for the RSA key... */ memcpy (kd.denom_keys, kd_old.denom_keys, kd_old.num_denom_keys * sizeof (struct TALER_EXCHANGE_DenomPublicKey)); for (unsigned int i = 0; iauditor_pub = aold->auditor_pub; GNUNET_assert (NULL != aold->auditor_url); anew->auditor_url = GNUNET_strdup (aold->auditor_url); GNUNET_array_grow (anew->denom_keys, anew->num_denom_keys, aold->num_denom_keys); memcpy (anew->denom_keys, aold->denom_keys, aold->num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); } /* Old auditors got just copied into new ones. */ if (GNUNET_OK != decode_keys_json (j, true, &kd, &vc)) { TALER_LOG_ERROR ("Could not decode /keys response\n"); hr.http_status = 0; hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; for (unsigned int i = 0; idenom_keys, anew->num_denom_keys, 0); GNUNET_free (anew->auditor_url); } GNUNET_free (kd.auditors); kd.auditors = NULL; kd.num_auditors = 0; for (unsigned int i = 0; ikey_data_raw); exchange->key_data_raw = json_deep_copy (j); exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; break; case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_UNAUTHORIZED: case MHD_HTTP_FORBIDDEN: case MHD_HTTP_NOT_FOUND: if (NULL == j) { hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; hr.hint = TALER_ErrorCode_get_hint (hr.ec); } else { hr.ec = TALER_JSON_get_error_code (j); hr.hint = TALER_JSON_get_error_hint (j); } break; default: if (MHD_HTTP_GATEWAY_TIMEOUT == response_code) exchange->keys_error_count++; if (NULL == j) { hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; hr.hint = TALER_ErrorCode_get_hint (hr.ec); } else { hr.ec = TALER_JSON_get_error_code (j); hr.hint = TALER_JSON_get_error_hint (j); } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, (int) hr.ec); break; } exchange->key_data = kd; if (GNUNET_TIME_absolute_is_past ( exchange->key_data.last_denom_issue_date.abs_time)) TALER_LOG_WARNING ("Last DK issue date from exchange is in the past: %s\n", GNUNET_TIME_timestamp2s ( exchange->key_data.last_denom_issue_date)); else TALER_LOG_DEBUG ("Last DK issue date updated to: %s\n", GNUNET_TIME_timestamp2s ( exchange->key_data.last_denom_issue_date)); if (MHD_HTTP_OK != response_code) { exchange->kr = NULL; free_keys_request (kr); exchange->state = MHS_FAILED; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange keys download failed\n"); if (NULL != exchange->key_data_raw) { json_decref (exchange->key_data_raw); exchange->key_data_raw = NULL; } free_key_data (&kd_old); /* notify application that we failed */ exchange->cert_cb (exchange->cert_cb_cls, &hr, NULL, vc); return; } exchange->kr = NULL; exchange->key_data_expiration = kr->expire; free_keys_request (kr); exchange->state = MHS_CERT; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Successfully downloaded exchange's keys\n"); update_auditors (exchange); /* notify application about the key information */ exchange->cert_cb (exchange->cert_cb_cls, &hr, &exchange->key_data, vc); free_key_data (&kd_old); } /* ********************* library internal API ********* */ struct GNUNET_CURL_Context * TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) { return h->ctx; } enum GNUNET_GenericReturnValue TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) { return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; } char * TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, const char *path) { GNUNET_assert ('/' == path[0]); return TALER_url_join (h->url, path + 1, NULL); } /** * Define a max length for the HTTP "Expire:" header */ #define MAX_DATE_LINE_LEN 32 /** * Parse HTTP timestamp. * * @param dateline header to parse header * @param at where to write the result * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue parse_date_string (const char *dateline, struct GNUNET_TIME_Timestamp *at) { static const char *MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; int year; int mon; int day; int hour; int min; int sec; char month[4]; struct tm tm; time_t t; /* We recognize the three formats in RFC2616, section 3.3.1. Month names are always in English. The formats are: Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format Note that the first is preferred. */ if (strlen (dateline) > MAX_DATE_LINE_LEN) { GNUNET_break_op (0); return GNUNET_SYSERR; } while (*dateline == ' ') ++dateline; while (*dateline && *dateline != ' ') ++dateline; while (*dateline == ' ') ++dateline; /* We just skipped over the day of the week. Now we have:*/ if ( (sscanf (dateline, "%d %3s %d %d:%d:%d", &day, month, &year, &hour, &min, &sec) != 6) && (sscanf (dateline, "%d-%3s-%d %d:%d:%d", &day, month, &year, &hour, &min, &sec) != 6) && (sscanf (dateline, "%3s %d %d:%d:%d %d", month, &day, &hour, &min, &sec, &year) != 6) ) { GNUNET_break (0); return GNUNET_SYSERR; } /* Two digit dates are defined to be relative to 1900; all other dates * are supposed to be represented as four digits. */ if (year < 100) year += 1900; for (mon = 0; ; mon++) { if (! MONTHS[mon]) { GNUNET_break_op (0); return GNUNET_SYSERR; } if (0 == strcasecmp (month, MONTHS[mon])) break; } memset (&tm, 0, sizeof(tm)); tm.tm_year = year - 1900; tm.tm_mon = mon; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = min; tm.tm_sec = sec; t = mktime (&tm); if (((time_t) -1) == t) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "mktime"); return GNUNET_SYSERR; } if (t < 0) t = 0; /* can happen due to timezone issues if date was 1.1.1970 */ *at = GNUNET_TIME_timestamp_from_s (t); return GNUNET_OK; } /** * Function called for each header in the HTTP /keys response. * Finds the "Expire:" header and parses it, storing the result * in the "expire" field of the keys request. * * @param buffer header data received * @param size size of an item in @a buffer * @param nitems number of items in @a buffer * @param userdata the `struct KeysRequest` * @return `size * nitems` on success (everything else aborts) */ static size_t header_cb (char *buffer, size_t size, size_t nitems, void *userdata) { struct KeysRequest *kr = userdata; size_t total = size * nitems; char *val; if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": ")) return total; if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ", buffer, strlen (MHD_HTTP_HEADER_EXPIRES ": "))) return total; val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")], total - strlen (MHD_HTTP_HEADER_EXPIRES ": ")); if (GNUNET_OK != parse_date_string (val, &kr->expire)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to parse %s-header `%s'\n", MHD_HTTP_HEADER_EXPIRES, val); kr->expire = GNUNET_TIME_UNIT_ZERO_TS; } GNUNET_free (val); return total; } /* ********************* public API ******************* */ /** * Deserialize the key data and use it to bootstrap @a exchange to * more efficiently recover the state. Errors in @a data must be * tolerated (i.e. by re-downloading instead). * * @param exchange which exchange's key and wire data should be deserialized * @param data the data to deserialize */ static void deserialize_data (struct TALER_EXCHANGE_Handle *exchange, const json_t *data) { enum TALER_EXCHANGE_VersionCompatibility vc; json_t *keys; const char *url; uint32_t version; struct GNUNET_TIME_Timestamp expire; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint32 ("version", &version), GNUNET_JSON_spec_json ("keys", &keys), GNUNET_JSON_spec_string ("exchange_url", &url), GNUNET_JSON_spec_timestamp ("expire", &expire), GNUNET_JSON_spec_end () }; struct TALER_EXCHANGE_Keys key_data; struct TALER_EXCHANGE_HttpResponse hr = { .ec = TALER_EC_NONE, .http_status = MHD_HTTP_OK, .reply = data }; if (NULL == data) return; if (GNUNET_OK != GNUNET_JSON_parse (data, spec, NULL, NULL)) { GNUNET_break_op (0); return; } if (0 != version) { GNUNET_JSON_parse_free (spec); return; /* unsupported version */ } if (0 != strcmp (url, exchange->url)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); return; } memset (&key_data, 0, sizeof (struct TALER_EXCHANGE_Keys)); if (GNUNET_OK != decode_keys_json (keys, false, &key_data, &vc)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); return; } /* decode successful, initialize with the result */ GNUNET_assert (NULL == exchange->key_data_raw); exchange->key_data_raw = json_deep_copy (keys); exchange->key_data = key_data; exchange->key_data_expiration = expire; exchange->state = MHS_CERT; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Successfully loaded exchange's keys via deserialization\n"); update_auditors (exchange); /* notify application about the key information */ exchange->cert_cb (exchange->cert_cb_cls, &hr, &exchange->key_data, vc); GNUNET_JSON_parse_free (spec); } json_t * TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) { const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; struct GNUNET_TIME_Timestamp now; json_t *keys; json_t *signkeys; json_t *denoms; json_t *auditors; now = GNUNET_TIME_timestamp_get (); signkeys = json_array (); if (NULL == signkeys) { GNUNET_break (0); return NULL; } for (unsigned int i = 0; inum_sign_keys; i++) { const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; json_t *signkey; if (GNUNET_TIME_timestamp_cmp (now, >, sk->valid_until)) continue; /* skip keys that have expired */ signkey = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("key", &sk->key), GNUNET_JSON_pack_data_auto ("master_sig", &sk->master_sig), GNUNET_JSON_pack_timestamp ("stamp_start", sk->valid_from), GNUNET_JSON_pack_timestamp ("stamp_expire", sk->valid_until), GNUNET_JSON_pack_timestamp ("stamp_end", sk->valid_legal)); if (NULL == signkey) { GNUNET_break (0); continue; } if (0 != json_array_append_new (signkeys, signkey)) { GNUNET_break (0); json_decref (signkey); json_decref (signkeys); return NULL; } } denoms = json_array (); if (NULL == denoms) { GNUNET_break (0); json_decref (signkeys); return NULL; } for (unsigned int i = 0; inum_denom_keys; i++) { const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; json_t *denom; if (GNUNET_TIME_timestamp_cmp (now, >, dk->expire_deposit)) continue; /* skip keys that have expired */ denom = GNUNET_JSON_PACK ( GNUNET_JSON_pack_timestamp ("stamp_expire_deposit", dk->expire_deposit), GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", dk->withdraw_valid_until), GNUNET_JSON_pack_timestamp ("stamp_start", dk->valid_from), GNUNET_JSON_pack_timestamp ("stamp_expire_legal", dk->expire_legal), TALER_JSON_pack_amount ("value", &dk->value), TALER_JSON_pack_amount ("fee_withdraw", &dk->fee_withdraw), TALER_JSON_pack_amount ("fee_deposit", &dk->fee_deposit), TALER_JSON_pack_amount ("fee_refresh", &dk->fee_refresh), TALER_JSON_pack_amount ("fee_refund", &dk->fee_refund), GNUNET_JSON_pack_data_auto ("master_sig", &dk->master_sig), TALER_JSON_pack_denom_pub ("denom_pub", &dk->key)); GNUNET_assert (0 == json_array_append_new (denoms, denom)); } auditors = json_array (); GNUNET_assert (NULL != auditors); for (unsigned int i = 0; inum_auditors; i++) { const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; json_t *a; json_t *adenoms; adenoms = json_array (); if (NULL == adenoms) { GNUNET_break (0); json_decref (denoms); json_decref (signkeys); json_decref (auditors); return NULL; } for (unsigned int j = 0; jnum_denom_keys; j++) { const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = &ai->denom_keys[j]; const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[adi->denom_key_offset]; json_t *k; if (GNUNET_TIME_timestamp_cmp (now, >, dk->expire_deposit)) continue; /* skip auditor signatures for denomination keys that have expired */ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); k = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("denom_pub_h", &dk->h_key), GNUNET_JSON_pack_data_auto ("auditor_sig", &adi->auditor_sig)); GNUNET_assert (0 == json_array_append_new (adenoms, k)); } a = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("auditor_pub", &ai->auditor_pub), GNUNET_JSON_pack_string ("auditor_url", ai->auditor_url), GNUNET_JSON_pack_array_steal ("denomination_keys", adenoms)); GNUNET_assert (0 == json_array_append_new (auditors, a)); } keys = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("version", kd->version), GNUNET_JSON_pack_string ("currency", kd->currency), GNUNET_JSON_pack_data_auto ("master_public_key", &kd->master_pub), GNUNET_JSON_pack_time_rel ("reserve_closing_delay", kd->reserve_closing_delay), GNUNET_JSON_pack_timestamp ("list_issue_date", kd->list_issue_date), GNUNET_JSON_pack_array_steal ("signkeys", signkeys), GNUNET_JSON_pack_array_steal ("denoms", denoms), GNUNET_JSON_pack_array_steal ("auditors", auditors)); return GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("version", EXCHANGE_SERIALIZATION_FORMAT_VERSION), GNUNET_JSON_pack_timestamp ("expire", exchange->key_data_expiration), GNUNET_JSON_pack_string ("exchange_url", exchange->url), GNUNET_JSON_pack_object_steal ("keys", keys)); } struct TALER_EXCHANGE_Handle * TALER_EXCHANGE_connect ( struct GNUNET_CURL_Context *ctx, const char *url, TALER_EXCHANGE_CertificationCallback cert_cb, void *cert_cb_cls, ...) { struct TALER_EXCHANGE_Handle *exchange; va_list ap; enum TALER_EXCHANGE_Option opt; TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n", url); /* Disable 100 continue processing */ GNUNET_break (GNUNET_OK == GNUNET_CURL_append_header (ctx, MHD_HTTP_HEADER_EXPECT ":")); exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); exchange->ctx = ctx; exchange->url = GNUNET_strdup (url); exchange->cert_cb = cert_cb; exchange->cert_cb_cls = cert_cb_cls; exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, exchange); va_start (ap, cert_cb_cls); while (TALER_EXCHANGE_OPTION_END != (opt = va_arg (ap, int))) { switch (opt) { case TALER_EXCHANGE_OPTION_END: GNUNET_assert (0); break; case TALER_EXCHANGE_OPTION_DATA: { const json_t *data = va_arg (ap, const json_t *); deserialize_data (exchange, data); break; } default: GNUNET_assert (0); break; } } va_end (ap); return exchange; } /** * Compute the network timeout for the next request to /keys. * * @param exchange the exchange handle * @returns the timeout in seconds (for use by CURL) */ static long get_keys_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange) { unsigned int kec; /* if retry counter >= 8, do not bother to go further, we stop the exponential back-off at 128 anyway. */ kec = GNUNET_MIN (7, exchange->keys_error_count); return GNUNET_MIN (120, 5 + (1L << kec)); } /** * Initiate download of /keys from the exchange. * * @param cls exchange where to download /keys from */ static void request_keys (void *cls) { struct TALER_EXCHANGE_Handle *exchange = cls; struct KeysRequest *kr; CURL *eh; char url[200] = "/keys?"; exchange->retry_task = NULL; GNUNET_assert (NULL == exchange->kr); kr = GNUNET_new (struct KeysRequest); kr->exchange = exchange; if (GNUNET_YES == TEAH_handle_is_ready (exchange)) { TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n", GNUNET_TIME_timestamp2s ( exchange->key_data.last_denom_issue_date)); sprintf (&url[strlen (url)], "last_issue_date=%llu&", (unsigned long long) exchange->key_data.last_denom_issue_date.abs_time.abs_value_us / 1000000LLU); } /* Clean the last '&'/'?' sign that we optimistically put. */ url[strlen (url) - 1] = '\0'; kr->url = TEAH_path_to_url (exchange, url); if (NULL == kr->url) { struct TALER_EXCHANGE_HttpResponse hr = { .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID }; GNUNET_free (kr); exchange->keys_error_count++; exchange->state = MHS_FAILED; exchange->cert_cb (exchange->cert_cb_cls, &hr, NULL, TALER_EXCHANGE_VC_PROTOCOL_ERROR); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Requesting keys with URL `%s'.\n", kr->url); eh = TALER_EXCHANGE_curl_easy_get_ (kr->url); if (NULL == eh) { GNUNET_free (kr->url); GNUNET_free (kr); exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, &request_keys, exchange); return; } GNUNET_break (CURLE_OK == curl_easy_setopt (eh, CURLOPT_VERBOSE, 0)); GNUNET_break (CURLE_OK == curl_easy_setopt (eh, CURLOPT_TIMEOUT, get_keys_timeout_seconds (exchange))); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HEADERFUNCTION, &header_cb)); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HEADERDATA, kr)); kr->job = GNUNET_CURL_job_add_with_ct_json (exchange->ctx, eh, &keys_completed_cb, kr); exchange->kr = kr; } void TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) { struct TEAH_AuditorListEntry *ale; while (NULL != (ale = exchange->auditors_head)) { struct TEAH_AuditorInteractionEntry *aie; while (NULL != (aie = ale->ai_head)) { GNUNET_assert (aie->ale == ale); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not sending deposit confirmation to auditor `%s' due to exchange disconnect\n", ale->auditor_url); TALER_AUDITOR_deposit_confirmation_cancel (aie->dch); GNUNET_CONTAINER_DLL_remove (ale->ai_head, ale->ai_tail, aie); GNUNET_free (aie); } GNUNET_CONTAINER_DLL_remove (exchange->auditors_head, exchange->auditors_tail, ale); TALER_LOG_DEBUG ("Disconnecting the auditor `%s'\n", ale->auditor_url); TALER_AUDITOR_disconnect (ale->ah); GNUNET_free (ale->auditor_url); GNUNET_free (ale); } if (NULL != exchange->kr) { GNUNET_CURL_job_cancel (exchange->kr->job); free_keys_request (exchange->kr); exchange->kr = NULL; } free_key_data (&exchange->key_data); if (NULL != exchange->key_data_raw) { json_decref (exchange->key_data_raw); exchange->key_data_raw = NULL; } if (NULL != exchange->retry_task) { GNUNET_SCHEDULER_cancel (exchange->retry_task); exchange->retry_task = NULL; } GNUNET_free (exchange->url); GNUNET_free (exchange); } enum GNUNET_GenericReturnValue TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, const struct TALER_ExchangePublicKeyP *pub) { struct GNUNET_TIME_Absolute now; /* we will check using a tolerance of 1h for the time */ now = GNUNET_TIME_absolute_get (); for (unsigned int i = 0; inum_sign_keys; i++) if ( (GNUNET_TIME_absolute_cmp ( keys->sign_keys[i].valid_from.abs_time, <=, GNUNET_TIME_absolute_add (now, LIFETIME_TOLERANCE))) && (GNUNET_TIME_absolute_cmp ( keys->sign_keys[i].valid_until.abs_time, >, GNUNET_TIME_absolute_subtract (now, LIFETIME_TOLERANCE))) && (0 == GNUNET_memcmp (pub, &keys->sign_keys[i].key)) ) return GNUNET_OK; GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Signing key not valid at time %s\n", GNUNET_TIME_absolute2s (now)); return GNUNET_SYSERR; } const char * TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange) { return exchange->url; } const struct TALER_EXCHANGE_DenomPublicKey * TALER_EXCHANGE_get_denomination_key ( const struct TALER_EXCHANGE_Keys *keys, const struct TALER_DenominationPublicKey *pk) { for (unsigned int i = 0; inum_denom_keys; i++) if (0 == TALER_denom_pub_cmp (pk, &keys->denom_keys[i].key)) return &keys->denom_keys[i]; return NULL; } struct TALER_EXCHANGE_DenomPublicKey * TALER_EXCHANGE_copy_denomination_key ( const struct TALER_EXCHANGE_DenomPublicKey *key) { struct TALER_EXCHANGE_DenomPublicKey *copy; copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey); *copy = *key; TALER_denom_pub_deep_copy (©->key, &key->key); return copy; } void TALER_EXCHANGE_destroy_denomination_key ( struct TALER_EXCHANGE_DenomPublicKey *key) { TALER_denom_pub_free (&key->key); GNUNET_free (key); } const struct TALER_EXCHANGE_DenomPublicKey * TALER_EXCHANGE_get_denomination_key_by_hash ( const struct TALER_EXCHANGE_Keys *keys, const struct TALER_DenominationHash *hc) { for (unsigned int i = 0; inum_denom_keys; i++) if (0 == GNUNET_memcmp (hc, &keys->denom_keys[i].h_key)) return &keys->denom_keys[i]; return NULL; } const struct TALER_EXCHANGE_Keys * TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange) { (void) TALER_EXCHANGE_check_keys_current (exchange, TALER_EXCHANGE_CKF_NONE); return &exchange->key_data; } json_t * TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange) { (void) TALER_EXCHANGE_check_keys_current (exchange, TALER_EXCHANGE_CKF_NONE); return json_deep_copy (exchange->key_data_raw); } /* end of exchange_api_handle.c */