aboutsummaryrefslogtreecommitdiff
path: root/src/lib/exchange_api_handle.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2019-01-11 21:27:34 +0100
committerChristian Grothoff <christian@grothoff.org>2019-01-11 21:43:15 +0100
commit54fc83ee6b910d482948c6ec8185df7aab1b0cb1 (patch)
tree10c04cad1392659a9ccef469271f866e393ebb48 /src/lib/exchange_api_handle.c
parent57ab9f9fdba607fcc3817adf58f37c5390f8d220 (diff)
fix cyclic dependency by combining exchange-lib and auditor-lib directories
Diffstat (limited to 'src/lib/exchange_api_handle.c')
-rw-r--r--src/lib/exchange_api_handle.c1779
1 files changed, 1779 insertions, 0 deletions
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
new file mode 100644
index 000000000..9743b1f09
--- /dev/null
+++ b/src/lib/exchange_api_handle.c
@@ -0,0 +1,1779 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 GNUnet e.V.
+
+ 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 exchange-lib/exchange_api_handle.c
+ * @brief Implementation of the "handle" component of the exchange's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_curl_defaults.h"
+#include "backoff.h"
+
+/**
+ * Which revision of the Taler protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define TALER_PROTOCOL_CURRENT 2
+
+/**
+ * How many revisions back are we compatible to?
+ */
+#define TALER_PROTOCOL_AGE 0
+
+/**
+ * Current version for (local) JSON serialization of persisted
+ * /keys data.
+ */
+#define TALER_SERIALIZATION_FORMAT_VERSION 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));
+
+/**
+ * Stages of initialization for the `struct TALER_EXCHANGE_Handle`
+ */
+enum ExchangeHandleState
+{
+ /**
+ * Just allocated.
+ */
+ MHS_INIT = 0,
+
+ /**
+ * Obtained the exchange's certification data and keys.
+ */
+ MHS_CERT = 1,
+
+ /**
+ * Failed to initialize (fatal).
+ */
+ MHS_FAILED = 2
+};
+
+
+/**
+ * Data for the request to get the /keys of a exchange.
+ */
+struct KeysRequest;
+
+
+/**
+ * Entry in list of ongoing interactions with an auditor.
+ */
+struct AuditorInteractionEntry
+{
+ /**
+ * DLL entry.
+ */
+ struct AuditorInteractionEntry *next;
+
+ /**
+ * DLL entry.
+ */
+ struct AuditorInteractionEntry *prev;
+
+ /**
+ * Interaction state.
+ */
+ struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+};
+
+
+/**
+ * Entry in DLL of auditors used by an exchange.
+ */
+struct AuditorListEntry
+{
+ /**
+ * Next pointer of DLL.
+ */
+ struct AuditorListEntry *next;
+
+ /**
+ * Prev pointer of DLL.
+ */
+ struct AuditorListEntry *prev;
+
+ /**
+ * Base URL of the auditor.
+ */
+ const char *auditor_url;
+
+ /**
+ * Handle to the auditor.
+ */
+ struct TALER_AUDITOR_Handle *ah;
+
+ /**
+ * Head of DLL of interactions with this auditor.
+ */
+ struct AuditorInteractionEntry *ai_head;
+
+ /**
+ * Tail of DLL of interactions with this auditor.
+ */
+ struct 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;
+
+};
+
+
+/**
+ * Handle to the exchange
+ */
+struct TALER_EXCHANGE_Handle
+{
+ /**
+ * The context of this handle
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The URL of the exchange (i.e. "http://exchange.taler.net/")
+ */
+ char *url;
+
+ /**
+ * Function to call with the exchange's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_EXCHANGE_CertificationCallback cert_cb;
+
+ /**
+ * Closure to pass to @e cert_cb.
+ */
+ void *cert_cb_cls;
+
+ /**
+ * Data for the request to get the /keys of a exchange,
+ * NULL once we are past stage #MHS_INIT.
+ */
+ struct KeysRequest *kr;
+
+ /**
+ * Task for retrying /keys request.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * Raw key data of the exchange, only valid if
+ * @e handshake_complete is past stage #MHS_CERT.
+ */
+ json_t *key_data_raw;
+
+ /**
+ * Head of DLL of auditors of this exchange.
+ */
+ struct AuditorListEntry *auditors_head;
+
+ /**
+ * Tail of DLL of auditors of this exchange.
+ */
+ struct AuditorListEntry *auditors_tail;
+
+ /**
+ * Key data of the exchange, only valid if
+ * @e handshake_complete is past stage #MHS_CERT.
+ */
+ struct TALER_EXCHANGE_Keys key_data;
+
+ /**
+ * Retry /keys frequency.
+ */
+ struct GNUNET_TIME_Relative retry_delay;
+
+ /**
+ * When does @e key_data expire?
+ */
+ struct GNUNET_TIME_Absolute key_data_expiration;
+
+ /**
+ * Stage of the exchange's initialization routines.
+ */
+ enum ExchangeHandleState state;
+
+};
+
+
+/* ***************** 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_Absolute expire;
+
+};
+
+
+/**
+ * Iterate over all available auditors for @a h, calling
+ * @param ah and giving it a chance to start a deposit
+ * confirmation interaction.
+ *
+ * @param h exchange to go over auditors for
+ * @param ac function to call per auditor
+ * @param ac_cls closure for @a ac
+ */
+void
+TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
+ TEAH_AuditorCallback ac,
+ void *ac_cls)
+{
+ // FIXME!
+}
+
+
+/**
+ * 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 int
+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_ExchangeSigningKeyValidityPS sign_key_issue;
+ 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_absolute_time ("stamp_start",
+ &sign_key->valid_from),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire",
+ &sign_key->valid_until),
+ GNUNET_JSON_spec_absolute_time ("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;
+ sign_key_issue.signkey_pub = sign_key->key;
+ sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY);
+ sign_key_issue.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS));
+ sign_key_issue.master_public_key = *master_key;
+ sign_key_issue.start = GNUNET_TIME_absolute_hton (sign_key->valid_from);
+ sign_key_issue.expire = GNUNET_TIME_absolute_hton (sign_key->valid_until);
+ sign_key_issue.end = GNUNET_TIME_absolute_hton (sign_key->valid_legal);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
+ &sign_key_issue.purpose,
+ &sign_key_issue_sig.eddsa_signature,
+ &master_key->eddsa_pub))
+ {
+ 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 int
+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 TALER_DenominationKeyValidityPS denom_key_issue;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &denom_key->master_sig),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit",
+ &denom_key->expire_deposit),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw",
+ &denom_key->withdraw_valid_until),
+ GNUNET_JSON_spec_absolute_time ("stamp_start",
+ &denom_key->valid_from),
+ GNUNET_JSON_spec_absolute_time ("stamp_expire_legal",
+ &denom_key->expire_legal),
+ TALER_JSON_spec_amount ("value",
+ &denom_key->value),
+ TALER_JSON_spec_amount ("fee_withdraw",
+ &denom_key->fee_withdraw),
+ TALER_JSON_spec_amount ("fee_deposit",
+ &denom_key->fee_deposit),
+ TALER_JSON_spec_amount ("fee_refresh",
+ &denom_key->fee_refresh),
+ TALER_JSON_spec_amount ("fee_refund",
+ &denom_key->fee_refund),
+ GNUNET_JSON_spec_rsa_public_key ("denom_pub",
+ &denom_key->key.rsa_public_key),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (denom_key_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key,
+ &denom_key->h_key);
+ if (! check_sigs)
+ return GNUNET_OK;
+ memset (&denom_key_issue,
+ 0,
+ sizeof (denom_key_issue));
+ denom_key_issue.purpose.purpose
+ = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
+ denom_key_issue.purpose.size
+ = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
+ denom_key_issue.master = *master_key;
+ denom_key_issue.denom_hash = denom_key->h_key;
+ denom_key_issue.start = GNUNET_TIME_absolute_hton (denom_key->valid_from);
+ denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until);
+ denom_key_issue.expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit);
+ denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal);
+ TALER_amount_hton (&denom_key_issue.value,
+ &denom_key->value);
+ TALER_amount_hton (&denom_key_issue.fee_withdraw,
+ &denom_key->fee_withdraw);
+ TALER_amount_hton (&denom_key_issue.fee_deposit,
+ &denom_key->fee_deposit);
+ TALER_amount_hton (&denom_key_issue.fee_refresh,
+ &denom_key->fee_refresh);
+ TALER_amount_hton (&denom_key_issue.fee_refund,
+ &denom_key->fee_refund);
+ EXITIF (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
+ &denom_key_issue.purpose,
+ &denom_key->master_sig.eddsa_signature,
+ &master_key->eddsa_pub));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &denom_key_issue.denom_hash,
+ sizeof (struct GNUNET_HashCode));
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ 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_sig 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 int
+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 TALER_ExchangeKeyValidityPS kv;
+ 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);
+ return GNUNET_SYSERR;
+ }
+ auditor->auditor_url = GNUNET_strdup (auditor_url);
+ kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS);
+ kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS));
+ GNUNET_CRYPTO_hash (auditor_url,
+ strlen (auditor_url) + 1,
+ &kv.auditor_url_hash);
+ kv.master = key_data->master_pub;
+ len = json_array_size (keys);
+ auditor->denom_keys = GNUNET_new_array (len,
+ struct TALER_EXCHANGE_AuditorDenominationInfo);
+ i = 0;
+ off = 0;
+ json_array_foreach (keys, i, key) {
+ struct TALER_AuditorSignatureP auditor_sig;
+ struct GNUNET_HashCode 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;j<key_data->num_denom_keys;j++)
+ {
+ if (0 == memcmp (&denom_h,
+ &key_data->denom_keys[j].h_key,
+ sizeof (struct GNUNET_HashCode)))
+ {
+ dk = &key_data->denom_keys[j];
+ dk_off = j;
+ break;
+ }
+ }
+ if (NULL == dk)
+ {
+ GNUNET_break_op (0);
+ continue;
+ }
+ if (check_sigs)
+ {
+ kv.start = GNUNET_TIME_absolute_hton (dk->valid_from);
+ kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until);
+ kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit);
+ kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal);
+ TALER_amount_hton (&kv.value,
+ &dk->value);
+ TALER_amount_hton (&kv.fee_withdraw,
+ &dk->fee_withdraw);
+ TALER_amount_hton (&kv.fee_deposit,
+ &dk->fee_deposit);
+ TALER_amount_hton (&kv.fee_refresh,
+ &dk->fee_refresh);
+ TALER_amount_hton (&kv.fee_refund,
+ &dk->fee_refund);
+ kv.denom_hash = dk->h_key;
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS,
+ &kv.purpose,
+ &auditor_sig.eddsa_sig,
+ &auditor->auditor_pub.eddsa_pub))
+ {
+ 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;
+}
+
+
+/**
+ * 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 #GNUNET_YES if we should check the signature
+ * @param[out] key_data where to store the results we decoded
+ * @param[out] where to store version compatibility data
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ * (malformed JSON)
+ */
+static int
+decode_keys_json (const json_t *resp_obj,
+ int 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;
+ unsigned int age;
+ unsigned int revision;
+ unsigned int current;
+ struct GNUNET_JSON_Specification mspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("eddsa_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("eddsa_pub",
+ &pub),
+ /* sig and pub must be first, as we skip those if
+ check_sig is false! */
+ GNUNET_JSON_spec_fixed_auto ("master_public_key",
+ &key_data->master_pub),
+ GNUNET_JSON_spec_absolute_time ("list_issue_date",
+ &key_data->list_issue_date),
+ GNUNET_JSON_spec_relative_time
+ ("reserve_closing_delay",
+ &key_data->reserve_closing_delay),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (JSON_OBJECT != json_typeof (resp_obj))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check the version */
+ {
+ const char *ver;
+ 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",
+ &current,
+ &revision,
+ &age))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *vc = TALER_EXCHANGE_VC_MATCH;
+ if (TALER_PROTOCOL_CURRENT < current)
+ {
+ *vc |= TALER_EXCHANGE_VC_NEWER;
+ if (TALER_PROTOCOL_CURRENT < current - age)
+ *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
+ }
+ if (TALER_PROTOCOL_CURRENT > current)
+ {
+ *vc |= TALER_EXCHANGE_VC_OLDER;
+ if (TALER_PROTOCOL_CURRENT - TALER_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));
+
+ /* 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_ARRAY != json_typeof (sign_keys_array));
+ EXITIF (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);
+ index = 0;
+ 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 denomination keys, merging with the
+ possibly EXISTING array as required (/keys cherry picking) */
+ {
+ json_t *denom_keys_array;
+ json_t *denom_key_obj;
+ unsigned int index;
+
+ EXITIF (NULL == (denom_keys_array =
+ json_object_get (resp_obj,
+ "denoms")));
+ EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
+
+ index = 0;
+ json_array_foreach (denom_keys_array, index, denom_key_obj) {
+ struct TALER_EXCHANGE_DenomPublicKey dk;
+ bool found = false;
+
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_denomkey (&dk,
+ check_sig,
+ denom_key_obj,
+ &key_data->master_pub,
+ hash_context));
+ for (unsigned int j=0;j<key_data->num_denom_keys;j++)
+ {
+ if (0 == memcmp (&dk,
+ &key_data->denom_keys[j],
+ sizeof (dk)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ /* 0:0:0 did not support /keys cherry picking */
+ GNUNET_break_op (0 == current);
+ 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 ("Crawling DK 'valid_from': %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (dk.valid_from));
+ key_data->last_denom_issue_date
+ = GNUNET_TIME_absolute_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) */
+ index = 0;
+ 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;j<key_data->num_auditors;j++)
+ {
+ struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j];
+
+ if (0 == memcmp (&ai.auditor_pub,
+ &aix->auditor_pub,
+ sizeof (struct TALER_AuditorPublicKeyP)))
+ {
+ found = true;
+ /* Merge denomination key signatures of downloaded /keys into existing
+ auditor information 'aix'. */
+ GNUNET_array_grow (aix->denom_keys,
+ aix->num_denom_keys,
+ aix->num_denom_keys + ai.num_denom_keys);
+ memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys],
+ ai.denom_keys,
+ ai.num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+ break;
+ }
+ }
+ if (found)
+ 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);
+ key_data->auditors[key_data->num_auditors++] = ai;
+ };
+ }
+
+ if (check_sig)
+ {
+ struct TALER_ExchangeKeySetPS ks;
+
+ /* Validate signature... */
+ ks.purpose.size = htonl (sizeof (ks));
+ ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET);
+ ks.list_issue_date = GNUNET_TIME_absolute_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.purpose,
+ &sig.eddsa_signature,
+ &pub.eddsa_pub));
+ }
+ return GNUNET_OK;
+ EXITIF_exit:
+
+ 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;i<key_data->num_denom_keys;i++)
+ GNUNET_CRYPTO_rsa_public_key_free (key_data->denom_keys[i].key.rsa_public_key);
+
+ GNUNET_array_grow (key_data->denom_keys,
+ key_data->denom_keys_size,
+ 0);
+ for (unsigned int i=0;i<key_data->num_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_non_null (key_data->version);
+ key_data->version = NULL;
+}
+
+
+/**
+ * Initiate download of /keys from the exchange.
+ *
+ * @param cls exchange where to download /keys from
+ */
+static void
+request_keys (void *cls);
+
+
+/**
+ * Check if our current response for /keys is valid, and if
+ * not trigger download.
+ *
+ * @param exchange exchange to check keys for
+ * @param force_download #GNUNET_YES to force download even if /keys is still valid
+ * @return until when the response is current, 0 if we are re-downloading
+ */
+struct GNUNET_TIME_Absolute
+TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
+ int force_download)
+{
+ if (NULL != exchange->kr)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ if ( (GNUNET_NO == force_download) &&
+ (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us) )
+ 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_ABS;
+}
+
+
+/**
+ * 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;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received keys from URL `%s' with status %ld.\n",
+ kr->url,
+ response_code);
+ kd_old = exchange->key_data;
+ memset (&kd,
+ 0,
+ sizeof (struct TALER_EXCHANGE_Keys));
+ vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
+ switch (response_code)
+ {
+ case 0:
+ free_keys_request (kr);
+ 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:
+ 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;i<kd_old.num_denom_keys;i++)
+ kd.denom_keys[i].key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (kd_old.denom_keys[i].key.rsa_public_key);
+
+ kd.num_auditors = kd_old.num_auditors;
+ kd.auditors = GNUNET_new_array (kd.num_auditors,
+ struct TALER_EXCHANGE_AuditorInformation);
+ /* Now the necessary deep copy... */
+ for (unsigned int i=0;i<kd_old.num_auditors;i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *aold = &kd_old.auditors[i];
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
+
+ anew->auditor_pub = aold->auditor_pub;
+ 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));
+ }
+
+ if (GNUNET_OK !=
+ decode_keys_json (j,
+ GNUNET_YES,
+ &kd,
+ &vc))
+ {
+ TALER_LOG_ERROR ("Could not decode /keys response\n");
+ response_code = 0;
+ for (unsigned int i=0;i<kd.num_auditors;i++)
+ {
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
+
+ GNUNET_array_grow (anew->denom_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;i<kd_old.num_denom_keys;i++)
+ GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key);
+ GNUNET_array_grow (kd.denom_keys,
+ kd.denom_keys_size,
+ 0);
+ kd.num_denom_keys = 0;
+ break;
+ }
+ json_decref (exchange->key_data_raw);
+ exchange->key_data_raw = json_deep_copy (j);
+ exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ break;
+ }
+ exchange->key_data = kd;
+ TALER_LOG_DEBUG ("Last DK issue date update to: %s\n",
+ GNUNET_STRINGS_absolute_time_to_string
+ (exchange->key_data.last_denom_issue_date));
+
+
+ if (MHD_HTTP_OK != response_code)
+ {
+ exchange->kr = NULL;
+ free_keys_request (kr);
+ exchange->state = MHS_FAILED;
+ 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,
+ NULL,
+ vc);
+ return;
+ }
+
+ exchange->kr = NULL;
+ exchange->key_data_expiration = kr->expire;
+ free_keys_request (kr);
+ exchange->state = MHS_CERT;
+ /* notify application about the key information */
+ exchange->cert_cb (exchange->cert_cb_cls,
+ &exchange->key_data,
+ vc);
+ free_key_data (&kd_old);
+}
+
+
+/* ********************* library internal API ********* */
+
+
+/**
+ * Get the context of a exchange.
+ *
+ * @param h the exchange handle to query
+ * @return ctx context to execute jobs in
+ */
+struct GNUNET_CURL_Context *
+TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h)
+{
+ return h->ctx;
+}
+
+
+/**
+ * Check if the handle is ready to process requests.
+ *
+ * @param h the exchange handle to query
+ * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
+ */
+int
+TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h)
+{
+ return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO;
+}
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param h handle for the exchange
+ * @param path Taler API path (i.e. "/reserve/withdraw")
+ * @return the full URL to use with cURL
+ */
+char *
+TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
+ const char *path)
+{
+ return TEAH_path_to_url2 (h->url,
+ path);
+}
+
+
+/**
+ * Obtain the URL to use for an API request.
+ *
+ * @param base_url base URL of the exchange (i.e. "http://exchange/")
+ * @param path Taler API path (i.e. "/reserve/withdraw")
+ * @return the full URL to use with cURL
+ */
+char *
+TEAH_path_to_url2 (const char *base_url,
+ const char *path)
+{
+ char *url;
+
+ if ( ('/' == path[0]) &&
+ (0 < strlen (base_url)) &&
+ ('/' == base_url[strlen (base_url) - 1]) )
+ path++; /* avoid generating URL with "//" from concat */
+ GNUNET_asprintf (&url,
+ "%s%s",
+ base_url,
+ path);
+ return url;
+}
+
+
+/**
+ * Parse HTTP timestamp.
+ *
+ * @param date header to parse header
+ * @param at where to write the result
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_date_string (const char *date,
+ struct GNUNET_TIME_Absolute *at)
+{
+ struct tm now;
+ time_t t;
+ const char *end;
+
+ memset (&now,
+ 0,
+ sizeof (now));
+ end = strptime (date,
+ "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */
+ &now);
+ if ( (NULL == end) ||
+ ( (*end != '\n') &&
+ (*end != '\r') ) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ t = mktime (&now);
+ 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->abs_value_us = 1000LL * 1000LL * 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 fo 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_ABS;
+ }
+ 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
+ * @return 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_Absolute expire;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("version",
+ &version),
+ GNUNET_JSON_spec_json ("keys",
+ &keys),
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_absolute_time ("expire",
+ &expire),
+ GNUNET_JSON_spec_end()
+ };
+ struct TALER_EXCHANGE_Keys key_data;
+
+ if (NULL == data)
+ return;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return;
+ }
+ if (0 != version)
+ return; /* unsupported version */
+ if (0 != strcmp (url,
+ exchange->url))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ memset (&key_data,
+ 0,
+ sizeof (struct TALER_EXCHANGE_Keys));
+ if (GNUNET_OK !=
+ decode_keys_json (keys,
+ GNUNET_NO,
+ &key_data,
+ &vc))
+ {
+ GNUNET_break (0);
+ 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;
+ /* notify application about the key information */
+ exchange->cert_cb (exchange->cert_cb_cls,
+ &exchange->key_data,
+ vc);
+}
+
+
+/**
+ * Serialize the latest key data from @a exchange to be persisted on
+ * disk (to be used with #TALER_EXCHANGE_OPTION_DATA to more
+ * efficiently recover the state).
+ *
+ * @param exchange which exchange's key and wire data should be
+ * serialized
+ * @return NULL on error (i.e. no current data available);
+ * otherwise JSON object owned by the caller
+ */
+json_t *
+TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
+{
+ const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
+ struct GNUNET_TIME_Absolute now;
+ json_t *keys;
+ json_t *signkeys;
+ json_t *denoms;
+ json_t *auditors;
+
+ now = GNUNET_TIME_absolute_get ();
+ signkeys = json_array ();
+ for (unsigned int i=0;i<kd->num_sign_keys;i++)
+ {
+ const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
+ json_t *signkey;
+
+ if (now.abs_value_us > sk->valid_until.abs_value_us)
+ continue; /* skip keys that have expired */
+ signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
+ "key",
+ GNUNET_JSON_from_data_auto (&sk->key),
+ "master_sig",
+ GNUNET_JSON_from_data_auto (&sk->master_sig),
+ "stamp_start",
+ GNUNET_JSON_from_time_abs (sk->valid_from),
+ "stamp_expire",
+ GNUNET_JSON_from_time_abs (sk->valid_until),
+ "stamp_end",
+ GNUNET_JSON_from_time_abs (sk->valid_legal));
+ if (NULL == signkey)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (signkeys,
+ signkey);
+ }
+ denoms = json_array ();
+ for (unsigned int i=0;i<kd->num_denom_keys;i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
+ json_t *denom;
+
+ if (now.abs_value_us > dk->expire_deposit.abs_value_us)
+ continue; /* skip keys that have expired */
+ denom = json_pack ("{s:o, s:o, s:o, s:o, s:o "
+ ",s:o, s:o, s:o, s:o, s:o "
+ ",s:o}",
+ "stamp_expire_deposit",
+ GNUNET_JSON_from_time_abs (dk->expire_deposit),
+ "stamp_expire_withdraw",
+ GNUNET_JSON_from_time_abs (dk->withdraw_valid_until),
+ "stamp_start",
+ GNUNET_JSON_from_time_abs (dk->valid_from),
+ "stamp_expire_legal",
+ GNUNET_JSON_from_time_abs (dk->expire_legal),
+ "value",
+ TALER_JSON_from_amount (&dk->value),
+ "fee_withdraw",
+ /* #6 */
+ TALER_JSON_from_amount (&dk->fee_withdraw),
+ "fee_deposit",
+ TALER_JSON_from_amount (&dk->fee_deposit),
+ "fee_refresh",
+ TALER_JSON_from_amount (&dk->fee_refresh),
+ "fee_refund",
+ TALER_JSON_from_amount (&dk->fee_refund),
+ "master_sig",
+ GNUNET_JSON_from_data_auto (&dk->master_sig),
+ /* #10 */
+ "denom_pub",
+ GNUNET_JSON_from_rsa_public_key (dk->key.rsa_public_key));
+ if (NULL == denom)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (denoms,
+ denom);
+ }
+ auditors = json_array ();
+ for (unsigned int i=0;i<kd->num_auditors;i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
+ json_t *a;
+ json_t *adenoms;
+
+ adenoms = json_array ();
+ for (unsigned int j=0;j<ai->num_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 (now.abs_value_us > dk->expire_deposit.abs_value_us)
+ continue; /* skip auditor signatures for denomination keys that have expired */
+ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+ k = json_pack ("{s:o, s:o}",
+ "denom_pub_h",
+ GNUNET_JSON_from_data_auto (&dk->h_key),
+ "auditor_sig",
+ GNUNET_JSON_from_data_auto (&adi->auditor_sig));
+ if (NULL == k)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (adenoms,
+ k);
+ }
+
+ a = json_pack ("{s:o, s:s, s:o}",
+ "auditor_pub",
+ GNUNET_JSON_from_data_auto (&ai->auditor_pub),
+ "auditor_url",
+ ai->auditor_url,
+ "denomination_keys",
+ adenoms);
+ if (NULL == a)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_append_new (auditors,
+ a);
+ }
+ keys = json_pack ("{s:s, s:o, s:o, s:o, s:o"
+ ",s:o, s:o}",
+ /* 1 */
+ "version",
+ kd->version,
+ "master_public_key",
+ GNUNET_JSON_from_data_auto (&kd->master_pub),
+ "reserve_closing_delay",
+ GNUNET_JSON_from_time_rel (kd->reserve_closing_delay),
+ "list_issue_date",
+ GNUNET_JSON_from_time_abs (kd->list_issue_date),
+ "signkeys",
+ signkeys,
+ /* #6 */
+ "denoms",
+ denoms,
+ "auditors",
+ auditors);
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return json_pack ("{s:I, s:o, s:s, s:o}",
+ "version",
+ (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION,
+ "expire",
+ GNUNET_JSON_from_time_abs (exchange->key_data_expiration),
+ "url",
+ exchange->url,
+ "keys",
+ keys);
+}
+
+
+/**
+ * Initialise a connection to the exchange. Will connect to the
+ * exchange and obtain information about the exchange's master
+ * public key and the exchange's auditor.
+ * The respective information will be passed to the @a cert_cb
+ * once available, and all future interactions with the exchange
+ * will be checked to be signed (where appropriate) by the
+ * respective master key.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param cert_cb function to call with the exchange's
+ * certification information
+ * @param cert_cb_cls closure for @a cert_cb
+ * @param ... list of additional arguments,
+ * terminated by #TALER_EXCHANGE_OPTION_END.
+ * @return the exchange handle; NULL upon error
+ */
+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;
+
+ 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;
+}
+
+
+/**
+ * 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;
+
+ 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))
+ {
+ char *arg;
+
+ TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (exchange->key_data.last_denom_issue_date));
+ GNUNET_asprintf (&arg,
+ "/keys?last_issue_date=%llu",
+ (unsigned long long) exchange->key_data.last_denom_issue_date.abs_value_us / 1000000LLU);
+ kr->url = TEAH_path_to_url (exchange,
+ arg);
+ GNUNET_free (arg);
+ }
+ else
+ {
+ kr->url = TEAH_path_to_url (exchange,
+ "/keys");
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting keys with URL `%s'.\n",
+ kr->url);
+ eh = TEL_curl_easy_get (kr->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ (long) 300));
+ 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 (exchange->ctx,
+ eh,
+ GNUNET_YES,
+ &keys_completed_cb,
+ kr);
+ exchange->kr = kr;
+}
+
+
+/**
+ * Disconnect from the exchange
+ *
+ * @param exchange the exchange handle
+ */
+void
+TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
+{
+ 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);
+}
+
+
+/**
+ * Lookup the given @a pub in @a keys.
+ *
+ * @param keys the exchange's key set
+ * @param pub claimed current online signing key for the exchange
+ * @return NULL if @a pub was not found
+ */
+const struct TALER_EXCHANGE_SigningPublicKey *
+TALER_EXCHANGE_get_signing_key_details (const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub)
+{
+ for (unsigned int i=0;i<keys->num_sign_keys;i++)
+ {
+ struct TALER_EXCHANGE_SigningPublicKey *spk = &keys->sign_keys[i];
+
+ if (0 == memcmp (pub,
+ &spk->key,
+ sizeof (struct TALER_ExchangePublicKeyP)))
+ return spk;
+ }
+ return NULL;
+}
+
+
+/**
+ * Test if the given @a pub is a the current signing key from the exchange
+ * according to @a keys.
+ *
+ * @param keys the exchange's key set
+ * @param pub claimed current online signing key for the exchange
+ * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key
+ */
+int
+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;i<keys->num_sign_keys;i++)
+ if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 * 60 * 1000LL * 1000LL) &&
+ (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 * 60 * 1000LL * 1000LL) &&
+ (0 == memcmp (pub,
+ &keys->sign_keys[i].key,
+ sizeof (struct TALER_ExchangePublicKeyP))) )
+ return GNUNET_OK;
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Get exchange's base URL.
+ *
+ * @param exchange exchange handle.
+ * @return the base URL from the handle.
+ */
+const char *
+TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange)
+{
+ return exchange->url;
+}
+
+
+/**
+ * Obtain the denomination key details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param pk public key of the denomination to lookup
+ * @return details about the given denomination key, NULL if the key is
+ * not found
+ */
+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;i<keys->num_denom_keys;i++)
+ if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key,
+ keys->denom_keys[i].key.rsa_public_key))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+/**
+ * Obtain the denomination key details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param hc hash of the public key of the denomination to lookup
+ * @return details about the given denomination key
+ */
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys,
+ const struct GNUNET_HashCode *hc)
+{
+ for (unsigned int i=0;i<keys->num_denom_keys;i++)
+ if (0 == memcmp (hc,
+ &keys->denom_keys[i].h_key,
+ sizeof (struct GNUNET_HashCode)))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+/**
+ * Obtain the keys from the exchange.
+ *
+ * @param exchange the exchange handle
+ * @return the exchange's key set
+ */
+const struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
+{
+ (void) TALER_EXCHANGE_check_keys_current (exchange,
+ GNUNET_NO);
+ return &exchange->key_data;
+}
+
+
+/**
+ * Obtain the keys from the exchange in the
+ * raw JSON format
+ *
+ * @param exchange the exchange handle
+ * @return the exchange's keys in raw JSON
+ */
+json_t *
+TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
+{
+ (void) TALER_EXCHANGE_check_keys_current (exchange,
+ GNUNET_NO);
+ return json_deep_copy (exchange->key_data_raw);
+}
+
+
+/* end of exchange_api_handle.c */