donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit 15e70b7ccc67b831200537db0ede96b78774a107
parent 3d530a74abc117377bdef7fcb6915df7c7bd5573
Author: Matyja Lukas Adam <lukas.matyja@students.bfh.ch>
Date:   Fri,  5 Jan 2024 15:19:09 +0100

[lib] restore donau_api_handle

Diffstat:
Msrc/donau/donau-httpd_keys.c | 10+++++-----
Msrc/lib/donau_api_handle.c | 1698+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1703 insertions(+), 5 deletions(-)

diff --git a/src/donau/donau-httpd_keys.c b/src/donau/donau-httpd_keys.c @@ -771,11 +771,11 @@ DH_keys_update_states () // .type = htons (TALER_DBEVENT_DONAU_KEYS_UPDATED), }; - // DH_plugin->event_notify (DH_plugin->cls, - // &es, - // NULL, - // 0); - // key_generation++; + DH_plugin->event_notify (DH_plugin->cls, + &es, + NULL, + 0); + key_generation++; // DH_resume_keys_requests (false); } diff --git a/src/lib/donau_api_handle.c b/src/lib/donau_api_handle.c @@ -127,3 +127,1700 @@ struct DONAU_GetKeysHandle do { \ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ } while (0) + +/** + * Parse a donau's signing key encoded in JSON. + * + * @param[out] sign_key where to return the result + * @param sign_key_obj json to parse + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the @a sign_key_obj is malformed. + */ +static enum GNUNET_GenericReturnValue +parse_json_signkey (struct DONAU_SigningPublicKeyAndValidity *sign_key, + const json_t *sign_key_obj) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &sign_key->master_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_donau_offline_signkey_validity_verify ( + &sign_key->key, + sign_key->valid_from, + sign_key->valid_until, + sign_key->valid_legal, + master_key, + &sign_key->master_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse a donau's denomination key encoded in JSON partially. + * + * Only the values for master_sig, timestamps and the cipher-specific public + * key are parsed. All other fields (fees, age_mask, value) MUST have been set + * prior to calling this function, otherwise the signature verification + * performed within this function will fail. + * + * @param[out] denom_key where to return the result + * @param cipher cipher type to parse + * @param check_sigs should we check signatures? + * @param denom_key_obj json to parse + * @param master_key master key to use to verify signature + * @param[in,out] hash_xor where to accumulate data for signature verification via XOR + * @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_partially ( + struct DONAU_DenomPublicKey *denom_key, + enum TALER_DenominationCipher cipher, + bool check_sigs, + const json_t *denom_key_obj, + struct TALER_MasterPublicKeyP *master_key, + struct GNUNET_HashCode *hash_xor) +{ + 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), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("lost", + &denom_key->lost), + NULL), + TALER_JSON_spec_denom_pub_cipher (NULL, + cipher, + &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_xor) + GNUNET_CRYPTO_hash_xor (&denom_key->h_key.hash, + hash_xor, + hash_xor); + if (! check_sigs) + return GNUNET_OK; + EXITIF (GNUNET_SYSERR == + TALER_donau_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->fees, + 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; +} + + +/** + * 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 DONAU_Keys *key_data, + enum DONAU_VersionCompatibility *vc) +{ + struct TALER_DonauSignatureP denominations_sig; + struct DONAU_DonauPublicKeyP pub; + const json_t *sign_keys_array; + const json_t *donation_units_by_group; + bool no_signature = false; + + 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 first */ + { + 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", + &current, + &revision, + &age, + &dummy)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *vc = DONAU_VC_MATCH; + if (DONAU_PROTOCOL_CURRENT < current) + { + *vc |= DONAU_VC_NEWER; + if (DONAU_PROTOCOL_CURRENT < current - age) + *vc |= DONAU_VC_INCOMPATIBLE; + } + if (DONAU_PROTOCOL_CURRENT > current) + { + *vc |= DONAU_VC_OLDER; + if (DONAU_PROTOCOL_CURRENT - DONAU_PROTOCOL_AGE > current) + *vc |= DONAU_VC_INCOMPATIBLE; + } + key_data->version = GNUNET_strdup (ver); + } + + { + const char *currency; + const char *asset_type; + struct GNUNET_JSON_Specification mspec[] = { + GNUNET_JSON_spec_fixed_auto ( + "denominations_sig", + &denominations_sig), + GNUNET_JSON_spec_fixed_auto ( + "eddsa_pub", + &pub), + GNUNET_JSON_spec_fixed_auto ( + "master_public_key", + &key_data->master_pub), + GNUNET_JSON_spec_array_const ("accounts", + &accounts), + GNUNET_JSON_spec_object_const ("wire_fees", + &fees), + GNUNET_JSON_spec_array_const ("wads", + &wads), + 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", + &currency), + GNUNET_JSON_spec_uint32 ( + "currency_fraction_digits", + &key_data->currency_fraction_digits), + GNUNET_JSON_spec_string ( + "asset_type", + &asset_type), + GNUNET_JSON_spec_array_const ( + "global_fees", + &global_fees), + GNUNET_JSON_spec_array_const ( + "signkeys", + &sign_keys_array), + GNUNET_JSON_spec_array_const ( + "denominations", + &denominations_by_group), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "recoup", + &recoup_array), + NULL), + GNUNET_JSON_spec_array_const ( + "auditors", + &auditors_array), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ( + "rewards_allowed", + &key_data->rewards_allowed), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("extensions", + &manifests), + &no_extensions), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ( + "extensions_sig", + &key_data->extensions_sig), + &no_signature), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "wallet_balance_limit_without_kyc", + &wblwk), + NULL), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + (check_sig) ? mspec : &mspec[2], + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Parsing /keys failed for `%s' (%u)\n", + emsg, + eline); + EXITIF (1); + } + { + struct GNUNET_JSON_Specification sspec[] = { + TALER_JSON_spec_amount ( + "stefan_abs", + currency, + &key_data->stefan_abs), + TALER_JSON_spec_amount ( + "stefan_log", + currency, + &key_data->stefan_log), + TALER_JSON_spec_amount ( + "stefan_lin", + currency, + &key_data->stefan_lin), + GNUNET_JSON_spec_end () + }; + + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + sspec, + NULL, NULL)); + } + + key_data->currency = GNUNET_strdup (currency); + key_data->asset_type = GNUNET_strdup (asset_type); + if (! no_extensions) + key_data->extensions = json_incref ((json_t *) manifests); + } + + /* parse the global fees */ + key_data->num_global_fees + = json_array_size (global_fees); + if (0 != key_data->num_global_fees) + { + json_t *global_fee; + unsigned int index; + + key_data->global_fees + = GNUNET_new_array (key_data->num_global_fees, + struct DONAU_GlobalFee); + json_array_foreach (global_fees, index, global_fee) + { + EXITIF (GNUNET_SYSERR == + parse_global_fee (&key_data->global_fees[index], + check_sig, + global_fee, + key_data)); + } + } + + /* parse the signing keys */ + key_data->num_sign_keys + = json_array_size (sign_keys_array); + if (0 != key_data->num_sign_keys) + { + json_t *sign_key_obj; + unsigned int index; + + key_data->sign_keys + = GNUNET_new_array (key_data->num_sign_keys, + struct DONAU_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 balance limits */ + if (NULL != wblwk) + { + key_data->wblwk_length = json_array_size (wblwk); + key_data->wallet_balance_limit_without_kyc + = GNUNET_new_array (key_data->wblwk_length, + struct TALER_Amount); + for (unsigned int i = 0; i<key_data->wblwk_length; i++) + { + struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i]; + const json_t *aj = json_array_get (wblwk, + i); + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount (NULL, + key_data->currency, + a), + GNUNET_JSON_spec_end () + }; + + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (aj, + spec, + NULL, NULL)); + } + } + + /* Parse wire accounts */ + key_data->fees = parse_fees (&key_data->master_pub, + key_data->currency, + fees, + &key_data->fees_len); + EXITIF (NULL == key_data->fees); + /* parse accounts */ + GNUNET_array_grow (key_data->accounts, + key_data->accounts_len, + json_array_size (accounts)); + EXITIF (GNUNET_OK != + DONAU_parse_accounts (&key_data->master_pub, + accounts, + key_data->accounts_len, + key_data->accounts)); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Parsed %u wire accounts from JSON\n", + (unsigned int) json_array_size (accounts)); + + + /* Parse the supported extension(s): age-restriction. */ + /* TODO: maybe lift all this into a FP in TALER_Extension ? */ + if (! no_extensions) + { + if (no_signature) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "found extensions without signature\n"); + } + else + { + /* We have an extensions object. Verify its signature. */ + EXITIF (GNUNET_OK != + TALER_extensions_verify_manifests_signature ( + manifests, + &key_data->extensions_sig, + &key_data->master_pub)); + + /* Parse and set the the configuration of the extensions accordingly */ + EXITIF (GNUNET_OK != + TALER_extensions_load_manifests (manifests)); + } + + /* Assuming we might have now a new value for age_mask, set it in key_data */ + key_data->age_mask = TALER_extensions_get_age_restriction_mask (); + } + + /* + * Parse the denomination keys, merging with the + * possibly EXISTING array as required (/keys cherry picking). + * + * The denominations are grouped by common values of + * {cipher, value, fee, age_mask}. + */ + { + json_t *group_obj; + unsigned int group_idx; + + json_array_foreach (denominations_by_group, group_idx, group_obj) + { + /* Running XOR of each SHA512 hash of the denominations' public key in + this group. Used to compare against group.hash after all keys have + been parsed. */ + struct GNUNET_HashCode group_hash_xor = {0}; + + /* First, parse { cipher, fees, value, age_mask, hash } of the current + group. */ + struct TALER_DenominationGroup group = {0}; + const json_t *denom_keys_array; + struct GNUNET_JSON_Specification group_spec[] = { + TALER_JSON_spec_denomination_group (NULL, + key_data->currency, + &group), + GNUNET_JSON_spec_array_const ("denoms", + &denom_keys_array), + GNUNET_JSON_spec_end () + }; + json_t *denom_key_obj; + unsigned int index; + + EXITIF (GNUNET_SYSERR == + GNUNET_JSON_parse (group_obj, + group_spec, + NULL, + NULL)); + + /* Now, parse the individual denominations */ + json_array_foreach (denom_keys_array, index, denom_key_obj) + { + /* Set the common fields from the group for this particular + denomination. Required to make the validity check inside + parse_json_denomkey_partially pass */ + struct DONAU_DenomPublicKey dk = { + .key.cipher = group.cipher, + .value = group.value, + .fees = group.fees, + .key.age_mask = group.age_mask + }; + bool found = false; + + EXITIF (GNUNET_SYSERR == + parse_json_denomkey_partially (&dk, + group.cipher, + check_sig, + denom_key_obj, + &key_data->master_pub, + check_sig ? &hash_xor : NULL)); + + /* Build the running xor of the SHA512-hash of the public keys for the group */ + GNUNET_CRYPTO_hash_xor (&dk.h_key.hash, + &group_hash_xor, + &group_hash_xor); + for (unsigned int j = 0; + j<key_data->num_denom_keys; + j++) + { + if (0 == GNUNET_CRYPTO_bsign_pub_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); + }; /* end of json_array_foreach over denominations */ + + /* The calculated group_hash_xor must be the same as group.hash from + the JSON. */ + EXITIF (0 != + GNUNET_CRYPTO_hash_cmp (&group_hash_xor, + &group.hash)); + + } /* end of json_array_foreach over groups of denominations */ + } /* end of scope for group_ojb/group_idx */ + + /* parse the auditor information */ + { + json_t *auditor_info; + unsigned int index; + + /* Merge with the existing auditor information we have (/keys cherry picking) */ + json_array_foreach (auditors_array, index, auditor_info) + { + struct DONAU_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 DONAU_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; i<ai.num_denom_keys; i++) + { + bool kfound = false; + + for (unsigned int k = 0; k<aix->num_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 */ + if (NULL != recoup_array) + { + json_t *recoup_info; + unsigned int index; + + json_array_foreach (recoup_array, index, recoup_info) + { + struct TALER_DenominationHashP 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; + j<key_data->num_denom_keys; + j++) + { + if (0 == GNUNET_memcmp (&h_denom_pub, + &key_data->denom_keys[j].h_key)) + { + key_data->denom_keys[j].revoked = true; + break; + } + } + } + } + + if (check_sig) + { + EXITIF (GNUNET_OK != + DONAU_test_signing_key (key_data, + &pub)); + EXITIF (GNUNET_OK != + TALER_donau_online_key_set_verify ( + key_data->list_issue_date, + &hash_xor, + &pub, + &denominations_sig)); + } + return GNUNET_OK; + +EXITIF_exit: + *vc = DONAU_VC_PROTOCOL_ERROR; + return GNUNET_SYSERR; +} + + +/** + * 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 DONAU_GetKeysHandle *gkh = cls; + const json_t *j = resp_obj; + struct DONAU_Keys *kd = NULL; + struct DONAU_KeysResponse kresp = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code, + .details.ok.compat = DONAU_VC_PROTOCOL_ERROR, + }; + + gkh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received keys from URL `%s' with status %ld and expiration %s.\n", + gkh->url, + response_code, + GNUNET_TIME_timestamp2s (gkh->expire)); + if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Donau failed to give expiration time, assuming in %s\n", + GNUNET_TIME_relative2s (DEFAULT_EXPIRATION, + true)); + gkh->expire + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION)); + } + switch (response_code) + { + case 0: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to receive /keys response from donau %s\n", + gkh->donau_url); + break; + case MHD_HTTP_OK: + if (NULL == j) + { + GNUNET_break (0); + response_code = 0; + break; + } + kd = GNUNET_new (struct DONAU_Keys); + kd->donau_url = GNUNET_strdup (gkh->donau_url); + if (NULL != gkh->prev_keys) + { + const struct DONAU_Keys *kd_old = gkh->prev_keys; + + /* 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... */ + GNUNET_memcpy (kd->denom_keys, + kd_old->denom_keys, + kd_old->num_denom_keys + * sizeof (struct DONAU_DenomPublicKey)); + for (unsigned int i = 0; i<kd_old->num_denom_keys; i++) + TALER_denom_pub_deep_copy (&kd->denom_keys[i].key, + &kd_old->denom_keys[i].key); + kd->num_auditors = kd_old->num_auditors; + kd->auditors = GNUNET_new_array (kd->num_auditors, + struct DONAU_AuditorInformation); + /* Now the necessary deep copy... */ + for (unsigned int i = 0; i<kd_old->num_auditors; i++) + { + const struct DONAU_AuditorInformation *aold = + &kd_old->auditors[i]; + struct DONAU_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); + GNUNET_memcpy ( + anew->denom_keys, + aold->denom_keys, + aold->num_denom_keys + * sizeof (struct DONAU_AuditorDenominationInfo)); + } + } + /* Now decode fresh /keys response */ + if (GNUNET_OK != + decode_keys_json (j, + true, + kd, + &kresp.details.ok.compat)) + { + TALER_LOG_ERROR ("Could not decode /keys response\n"); + kd->rc = 1; + DONAU_keys_decref (kd); + kd = NULL; + kresp.hr.http_status = 0; + kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + kd->rc = 1; + kd->key_data_expiration = gkh->expire; + if (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time), + <, + MINIMUM_EXPIRATION)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Donau returned keys with expiration time below %s. Compensating.\n", + GNUNET_TIME_relative2s (MINIMUM_EXPIRATION, + true)); + kd->key_data_expiration + = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION); + } + + kresp.details.ok.keys = kd; + break; + case MHD_HTTP_BAD_REQUEST: + case MHD_HTTP_UNAUTHORIZED: + case MHD_HTTP_FORBIDDEN: + case MHD_HTTP_NOT_FOUND: + if (NULL == j) + { + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); + } + else + { + kresp.hr.ec = TALER_JSON_get_error_code (j); + kresp.hr.hint = TALER_JSON_get_error_hint (j); + } + break; + default: + if (NULL == j) + { + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); + } + else + { + kresp.hr.ec = TALER_JSON_get_error_code (j); + kresp.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) kresp.hr.ec); + break; + } + gkh->cert_cb (gkh->cert_cb_cls, + &kresp, + kd); + DONAU_get_keys_cancel (gkh); +} + + +/** + * 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[out] 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 DONAU_GetKeysHandle` + * @return `size * nitems` on success (everything else aborts) + */ +static size_t +header_cb (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct DONAU_GetKeysHandle *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 ": ")); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Found %s header `%s'\n", + MHD_HTTP_HEADER_EXPIRES, + val); + 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; +} + + +struct DONAU_GetKeysHandle * +DONAU_get_keys ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct DONAU_Keys *last_keys, + DONAU_GetKeysCallback cert_cb, + void *cert_cb_cls) +{ + struct DONAU_GetKeysHandle *gkh; + CURL *eh; + + TALER_LOG_DEBUG ("Connecting to the donau (%s)\n", + url); + gkh = GNUNET_new (struct DONAU_GetKeysHandle); + gkh->donau_url = GNUNET_strdup (url); + gkh->cert_cb = cert_cb; + gkh->cert_cb_cls = cert_cb_cls; + gkh->url = TALER_url_join (url, + "keys"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting keys with URL `%s'.\n", + gkh->url); + eh = DONAU_curl_easy_get_ (gkh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (gkh->donau_url); + GNUNET_free (gkh->url); + GNUNET_free (gkh); + return NULL; + } + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + 120 /* seconds */)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &header_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + gkh)); + gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx, + eh, + &keys_completed_cb, + gkh); + return gkh; +} + + +void +DONAU_get_keys_cancel ( + struct DONAU_GetKeysHandle *gkh) +{ + if (NULL != gkh->job) + { + GNUNET_CURL_job_cancel (gkh->job); + gkh->job = NULL; + } + DONAU_keys_decref (gkh->prev_keys); + GNUNET_free (gkh->donau_url); + GNUNET_free (gkh->url); + GNUNET_free (gkh); +} + + +enum GNUNET_GenericReturnValue +DONAU_test_signing_key ( + const struct DONAU_Keys *keys, + const struct DONAU_DonauPublicKeyP *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 ( (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 struct DONAU_DenomPublicKey * +DONAU_get_denomination_key ( + const struct DONAU_Keys *keys, + const struct TALER_DenominationPublicKey *pk) +{ + for (unsigned int i = 0; i<keys->num_denom_keys; i++) + if (0 == + TALER_denom_pub_cmp (pk, + &keys->denom_keys[i].key)) + return &keys->denom_keys[i]; + return NULL; +} + + +struct DONAU_DenomPublicKey * +DONAU_copy_denomination_key ( + const struct DONAU_DenomPublicKey *key) +{ + struct DONAU_DenomPublicKey *copy; + + copy = GNUNET_new (struct DONAU_DenomPublicKey); + *copy = *key; + TALER_denom_pub_deep_copy (&copy->key, + &key->key); + return copy; +} + + +void +DONAU_destroy_denomination_key ( + struct DONAU_DenomPublicKey *key) +{ + TALER_denom_pub_free (&key->key); + GNUNET_free (key); +} + + +const struct DONAU_DenomPublicKey * +DONAU_get_denomination_key_by_hash ( + const struct DONAU_Keys *keys, + const struct TALER_DenominationHashP *hc) +{ + for (unsigned int i = 0; i<keys->num_denom_keys; i++) + if (0 == GNUNET_memcmp (hc, + &keys->denom_keys[i].h_key)) + return &keys->denom_keys[i]; + return NULL; +} + + +struct DONAU_Keys * +DONAU_keys_incref (struct DONAU_Keys *keys) +{ + GNUNET_assert (keys->rc < UINT_MAX); + keys->rc++; + return keys; +} + + +void +DONAU_keys_decref (struct DONAU_Keys *keys) +{ + if (NULL == keys) + return; + GNUNET_assert (0 < keys->rc); + keys->rc--; + if (0 != keys->rc) + return; + GNUNET_array_grow (keys->sign_keys, + keys->num_sign_keys, + 0); + for (unsigned int i = 0; i<keys->num_denom_keys; i++) + TALER_denom_pub_free (&keys->denom_keys[i].key); + + GNUNET_array_grow (keys->denom_keys, + keys->denom_keys_size, + 0); + for (unsigned int i = 0; i<keys->num_auditors; i++) + { + GNUNET_array_grow (keys->auditors[i].denom_keys, + keys->auditors[i].num_denom_keys, + 0); + GNUNET_free (keys->auditors[i].auditor_url); + } + GNUNET_array_grow (keys->auditors, + keys->auditors_size, + 0); + DONAU_free_accounts (keys->accounts_len, + keys->accounts); + GNUNET_array_grow (keys->accounts, + keys->accounts_len, + 0); + free_fees (keys->fees, + keys->fees_len); + json_decref (keys->extensions); + GNUNET_free (keys->wallet_balance_limit_without_kyc); + GNUNET_free (keys->version); + GNUNET_free (keys->currency); + GNUNET_free (keys->asset_type); + GNUNET_free (keys->global_fees); + GNUNET_free (keys->donau_url); + GNUNET_free (keys); +} + + +struct DONAU_Keys * +DONAU_keys_from_json (const json_t *j) +{ + const json_t *jkeys; + const char *url; + uint32_t version; + struct GNUNET_TIME_Timestamp expire + = GNUNET_TIME_UNIT_ZERO_TS; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("version", + &version), + GNUNET_JSON_spec_object_const ("keys", + &jkeys), + GNUNET_JSON_spec_string ("donau_url", + &url), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("expire", + &expire), + NULL), + GNUNET_JSON_spec_end () + }; + struct DONAU_Keys *keys; + enum DONAU_VersionCompatibility compat; + + if (NULL == j) + return NULL; + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return NULL; + } + if (0 != version) + { + return NULL; /* unsupported version */ + } + keys = GNUNET_new (struct DONAU_Keys); + if (GNUNET_OK != + decode_keys_json (jkeys, + false, + keys, + &compat)) + { + GNUNET_break (0); + return NULL; + } + keys->rc = 1; + keys->key_data_expiration = expire; + keys->donau_url = GNUNET_strdup (url); + return keys; +} + + +/** + * Data we track per donation unit group. + */ +struct GroupData +{ + /** + * The json blob with the group meta-data and list of donation units + */ + json_t *json; + + /** + * Meta data for this group. + */ + struct DONAU_DonationUnitGroup meta; +}; + + +/** + * Add donation unit group represented by @a value + * to list of donation units in @a cls. Also frees + * the @a value. + * + * @param[in,out] cls a `json_t *` with an array to build + * @param key unused + * @param value a `struct GroupData *` + * @return #GNUNET_OK (continue to iterate) + */ +static enum GNUNET_GenericReturnValue +add_grp (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + json_t *donation_units_by_group = cls; + struct GroupData *gd = value; + const char *cipher; + json_t *ge; + + (void) key; + switch (gd->meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + cipher = age_restricted ? "RSA+age_restricted" : "RSA"; + break; + case GNUNET_CRYPTO_BSA_CS: + cipher = age_restricted ? "CS+age_restricted" : "CS"; + break; + default: + GNUNET_assert (false); + } + + ge = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("hash", + &gd->meta.hash), + GNUNET_JSON_pack_string ("cipher", + cipher), + GNUNET_JSON_pack_array_steal ("denoms", + gd->json), + TALER_JSON_pack_amount ("value", + &gd->meta.value)); + GNUNET_assert (0 == + json_array_append_new (donation_units_by_group, + ge)); + GNUNET_free (gd); + return GNUNET_OK; +} + + +/** + * Convert array of account restrictions @a ars to JSON. + * + * @param ar_len length of @a ars + * @param ars account restrictions to convert + * @return JSON representation + */ +static json_t * +ar_to_json (unsigned int ar_len, + const struct TALER_DONAU_AccountRestriction ars[static ar_len]) +{ + json_t *rval; + + rval = json_array (); + GNUNET_assert (NULL != rval); + for (unsigned int i = 0; i<ar_len; i++) + { + const struct TALER_DONAU_AccountRestriction *ar = &ars[i]; + + switch (ar->type) + { + case TALER_DONAU_AR_INVALID: + GNUNET_break (0); + json_decref (rval); + return NULL; + case TALER_DONAU_AR_DENY: + GNUNET_assert ( + 0 == + json_array_append_new ( + rval, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "deny")))); + break; + case TALER_DONAU_AR_REGEX: + GNUNET_assert ( + 0 == + json_array_append_new ( + rval, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "type", + "regex"), + GNUNET_JSON_pack_string ( + "regex", + ar->details.regex.posix_egrep), + GNUNET_JSON_pack_string ( + "human_hint", + ar->details.regex.human_hint), + GNUNET_JSON_pack_object_incref ( + "human_hint_i18n", + (json_t *) ar->details.regex.human_hint_i18n) + ))); + break; + } + } + return rval; +} + + +json_t * +DONAU_keys_to_json (const struct DONAU_Keys *kd) +{ + json_t *keys; + json_t *signkeys; + json_t *donation_units; + json_t *accounts; + + now = GNUNET_TIME_timestamp_get (); + signkeys = json_array (); + GNUNET_assert (NULL != signkeys); + for (unsigned int i = 0; i<kd->num_sign_keys; i++) + { + const struct TALER_DONAU_SigningPublicKey *sk = &kd->sign_keys[i]; + json_t *signkey; + + signkey = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("key", + &sk->key), + GNUNET_JSON_pack_uint64 ("year", + &sk->year)); + GNUNET_assert (NULL != signkey); + GNUNET_assert (0 == + json_array_append_new (signkeys, + signkey)); + } + + donation_units_by_group = json_array (); + GNUNET_assert (NULL != donation_units_by_group); + { + struct GNUNET_CONTAINER_MultiHashMap *dbg; + + dbg = GNUNET_CONTAINER_multihashmap_create (128, + false); + for (unsigned int i = 0; i<kd->num_donation_unit_keys; i++) + { + const struct TALER_DONAU_DenomPublicKey *dk = &kd->denom_keys[i]; + struct TALER_DenominationGroup meta = { + .cipher = dk->key.cipher, + .value = dk->value, + .year = dk->year + }; + struct GNUNET_HashCode key; + struct GroupData *gd; + json_t *donation_unit; + struct GNUNET_JSON_PackSpec key_spec; + + //TODO: check year + + TALER_donation_unit_group_get_key (&meta, + &key); + gd = GNUNET_CONTAINER_multihashmap_get (dbg, + &key); + if (NULL == gd) + { + gd = GNUNET_new (struct GroupData); + gd->meta = meta; + gd->json = json_array (); + GNUNET_assert (NULL != gd->json); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (dbg, + &key, + gd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + + } + + switch (meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + key_spec = + GNUNET_JSON_pack_rsa_public_key ( + "rsa_pub", + dk->key.bsign_pub_key->details.rsa_public_key); + break; + case GNUNET_CRYPTO_BSA_CS: + key_spec = + GNUNET_JSON_pack_data_varsize ( + "cs_pub", + &dk->key.bsign_pub_key->details.cs_public_key, + sizeof (dk->key.bsign_pub_key->details.cs_public_key)); + break; + default: + GNUNET_assert (false); + } + donation_unit = 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), + GNUNET_JSON_pack_data_auto ("master_sig", + &dk->master_sig), + key_spec + ); + GNUNET_assert (0 == + json_array_append_new (gd->json, + donation_unit)); + } + GNUNET_CONTAINER_multihashmap_iterate (dbg, + &add_grp, + donation_unit_by_group); + GNUNET_CONTAINER_multihashmap_destroy (dbg); + } + + auditors = json_array (); + GNUNET_assert (NULL != auditors); + for (unsigned int i = 0; i<kd->num_auditors; i++) + { + const struct TALER_DONAU_AuditorInformation *ai = &kd->auditors[i]; + json_t *a; + json_t *adenoms; + + adenoms = json_array (); + GNUNET_assert (NULL != adenoms); + for (unsigned int j = 0; j<ai->num_denom_keys; j++) + { + const struct TALER_DONAU_AuditorDenominationInfo *adi = + &ai->denom_keys[j]; + const struct TALER_DONAU_DenomPublicKey *dk = + &kd->denom_keys[adi->denom_key_offset]; + json_t *k; + + GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); + 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)); + } + + global_fees = json_array (); + GNUNET_assert (NULL != global_fees); + for (unsigned int i = 0; i<kd->num_global_fees; i++) + { + const struct TALER_DONAU_GlobalFee *gf + = &kd->global_fees[i]; + + if (GNUNET_TIME_absolute_is_past (gf->end_date.abs_time)) + continue; + GNUNET_assert ( + 0 == + json_array_append_new ( + global_fees, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("start_date", + gf->start_date), + GNUNET_JSON_pack_timestamp ("end_date", + gf->end_date), + TALER_JSON_PACK_GLOBAL_FEES (&gf->fees), + GNUNET_JSON_pack_time_rel ("history_expiration", + gf->history_expiration), + GNUNET_JSON_pack_time_rel ("purse_timeout", + gf->purse_timeout), + GNUNET_JSON_pack_uint64 ("purse_account_limit", + gf->purse_account_limit), + GNUNET_JSON_pack_data_auto ("master_sig", + &gf->master_sig)))); + } + + accounts = json_array (); + GNUNET_assert (NULL != accounts); + for (unsigned int i = 0; i<kd->accounts_len; i++) + { + const struct TALER_DONAU_WireAccount *acc + = &kd->accounts[i]; + json_t *credit_restrictions; + json_t *debit_restrictions; + + credit_restrictions + = ar_to_json (acc->credit_restrictions_length, + acc->credit_restrictions); + GNUNET_assert (NULL != credit_restrictions); + debit_restrictions + = ar_to_json (acc->debit_restrictions_length, + acc->debit_restrictions); + GNUNET_assert (NULL != debit_restrictions); + GNUNET_assert ( + 0 == + json_array_append_new ( + accounts, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + acc->payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + acc->conversion_url)), + GNUNET_JSON_pack_array_steal ("debit_restrictions", + debit_restrictions), + GNUNET_JSON_pack_array_steal ("credit_restrictions", + credit_restrictions), + GNUNET_JSON_pack_data_auto ("master_sig", + &acc->master_sig)))); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Serialized %u/%u wire accounts to JSON\n", + (unsigned int) json_array_size (accounts), + kd->accounts_len); + + wire_fees = json_object (); + GNUNET_assert (NULL != wire_fees); + for (unsigned int i = 0; i<kd->fees_len; i++) + { + const struct TALER_DONAU_WireFeesByMethod *fbw + = &kd->fees[i]; + json_t *wf; + + wf = json_array (); + GNUNET_assert (NULL != wf); + for (struct TALER_DONAU_WireAggregateFees *p = fbw->fees_head; + NULL != p; + p = p->next) + { + GNUNET_assert ( + 0 == + json_array_append_new ( + wf, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("wire_fee", + &p->fees.wire), + TALER_JSON_pack_amount ("closing_fee", + &p->fees.closing), + GNUNET_JSON_pack_timestamp ("start_date", + p->start_date), + GNUNET_JSON_pack_timestamp ("end_date", + p->end_date), + GNUNET_JSON_pack_data_auto ("sig", + &p->master_sig)))); + } + GNUNET_assert (0 == + json_object_set_new (wire_fees, + fbw->method, + wf)); + } + + recoup = json_array (); + GNUNET_assert (NULL != recoup); + for (unsigned int i = 0; i<kd->num_denom_keys; i++) + { + const struct TALER_DONAU_DenomPublicKey *dk + = &kd->denom_keys[i]; + if (! dk->revoked) + continue; + GNUNET_assert (0 == + json_array_append_new ( + recoup, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_denom_pub", + &dk->h_key)))); + } + + wblwk = json_array (); + GNUNET_assert (NULL != wblwk); + for (unsigned int i = 0; i<kd->wblwk_length; i++) + { + const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i]; + + GNUNET_assert (0 == + json_array_append_new ( + wblwk, + TALER_JSON_from_amount (a))); + } + + keys = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("version", + kd->version), + GNUNET_JSON_pack_string ("currency", + kd->currency), + GNUNET_JSON_pack_uint64 ("currency_fraction_digits", + kd->currency_fraction_digits), + GNUNET_JSON_pack_array_steal ("signkeys", + signkeys), + GNUNET_JSON_pack_array_steal ("donation_units", + donation_units_by_group) + ); + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("version", + DONAU_SERIALIZATION_FORMAT_VERSION), + GNUNET_JSON_pack_string ("donau_url", + kd->donau_url), + GNUNET_JSON_pack_object_steal ("keys", + keys)); +} + + +/* end of donau_api_handle.c */ +\ No newline at end of file