/* This file is part of TALER Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, If not, see */ /** * @file mint-lib/mint_api_handle.c * @brief Implementation of the "handle" component of the mint's HTTP API * @author Sree Harsha Totakura * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include "taler_mint_service.h" #include "taler_signatures.h" #include "mint_api_context.h" #include "mint_api_json.h" #include "mint_api_handle.h" /** * 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)); /** * Print JSON parsing related error information */ #define JSON_WARN(error) \ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ "JSON parsing failed at %s:%u: %s (%s)", \ __FILE__, __LINE__, error.text, error.source) /** * Stages of initialization for the `struct TALER_MINT_Handle` */ enum MintHandleState { /** * Just allocated. */ MHS_INIT = 0, /** * Obtained the mint'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 mint. */ struct KeysRequest; /** * Handle to the mint */ struct TALER_MINT_Handle { /** * The context of this handle */ struct TALER_MINT_Context *ctx; /** * The URL of the mint (i.e. "http://mint.taler.net/") */ char *url; /** * Function to call with the mint's certification data, * NULL if this has already been done. */ TALER_MINT_CertificationCallback cert_cb; /** * Closure to pass to @e cert_cb. */ void *cert_cb_cls; /** * Data for the request to get the /keys of a mint, * NULL once we are past stage #MHS_INIT. */ struct KeysRequest *kr; /** * Key data of the mint, only valid if * @e handshake_complete is past stage #MHS_CERT. */ struct TALER_MINT_Keys key_data; /** * Stage of the mint's initialization routines. */ enum MintHandleState state; }; /* ***************** Internal /keys fetching ************* */ /** * Data for the request to get the /keys of a mint. */ struct KeysRequest { /** * The connection to mint this request handle will use */ struct TALER_MINT_Handle *mint; /** * The url for this handle */ char *url; /** * Entry for this request with the `struct TALER_MINT_Context`. */ struct MAC_Job *job; /** * Error buffer for Curl. Do we need this? */ char emsg[CURL_ERROR_SIZE]; /** * Download buffer */ void *buf; /** * The size of the download buffer */ size_t buf_size; /** * Error code (based on libc errno) if we failed to download * (i.e. response too large). */ int eno; }; /** * Callback used when downloading the reply to a /keys request. * Just appends all of the data to the `buf` in the * `struct KeysRequest` for further processing. The size of * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if * the download exceeds this size, we abort with an error. * * @param bufptr data downloaded via HTTP * @param size size of an item in @a bufptr * @param nitems number of items in @a bufptr * @param cls the `struct KeysRequest` * @return number of bytes processed from @a bufptr */ static size_t keys_download_cb (char *bufptr, size_t size, size_t nitems, void *cls) { struct KeysRequest *kr = cls; size_t msize; void *buf; if (0 == size * nitems) { /* Nothing (left) to do */ return 0; } msize = size * nitems; if ( (msize + kr->buf_size) >= GNUNET_MAX_MALLOC_CHECKED) { kr->eno = ENOMEM; return 0; /* signals an error to curl */ } kr->buf = GNUNET_realloc (kr->buf, kr->buf_size + msize); buf = kr->buf + kr->buf_size; memcpy (buf, bufptr, msize); kr->buf_size += msize; return msize; } /** * 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_non_null (kr->buf); GNUNET_free (kr->url); GNUNET_free (kr); } #define EXITIF(cond) \ do { \ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ } while (0) /** * Parse a mint's signing key encoded in JSON. * * @param[out] sign_key where to return the result * @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_MINT_SigningPublicKey *sign_key, json_t *sign_key_obj, const struct TALER_MasterPublicKeyP *master_key) { struct TALER_MintSigningKeyValidityPS sign_key_issue; struct GNUNET_CRYPTO_EddsaSignature sig; struct GNUNET_TIME_Absolute valid_from; struct GNUNET_TIME_Absolute valid_until; struct GNUNET_TIME_Absolute valid_legal; struct MAJ_Specification spec[] = { MAJ_spec_fixed_auto ("master_sig", &sig), MAJ_spec_fixed_auto ("key", &sign_key_issue.signkey_pub), MAJ_spec_absolute_time ("stamp_start", &valid_from), MAJ_spec_absolute_time ("stamp_expire", &valid_until), MAJ_spec_absolute_time ("stamp_end", &valid_legal), MAJ_spec_end }; if (GNUNET_OK != MAJ_parse_json (sign_key_obj, spec)) { GNUNET_break_op (0); return GNUNET_SYSERR; } sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); sign_key_issue.purpose.size = htonl (sizeof (sign_key_issue) - offsetof (struct TALER_MintSigningKeyValidityPS, purpose)); sign_key_issue.master_public_key = *master_key; sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until); sign_key_issue.end = GNUNET_TIME_absolute_hton (valid_legal); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, &sign_key_issue.purpose, &sig, &master_key->eddsa_pub)) { GNUNET_break_op (0); return GNUNET_SYSERR; } sign_key->valid_from = valid_from; sign_key->valid_until = valid_until; sign_key->key = sign_key_issue.signkey_pub; return GNUNET_OK; } /** * Parse a mint's denomination key encoded in JSON. * * @param[out] denom_key where to return the result * @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_MINT_DenomPublicKey *denom_key, json_t *denom_key_obj, struct TALER_MasterPublicKeyP *master_key, struct GNUNET_HashContext *hash_context) { struct GNUNET_TIME_Absolute valid_from; struct GNUNET_TIME_Absolute withdraw_valid_until; struct GNUNET_TIME_Absolute deposit_valid_until; struct GNUNET_TIME_Absolute expire_legal; struct TALER_Amount value; struct TALER_Amount fee_withdraw; struct TALER_Amount fee_deposit; struct TALER_Amount fee_refresh; struct TALER_DenominationKeyValidityPS denom_key_issue; struct GNUNET_CRYPTO_rsa_PublicKey *pk; struct GNUNET_CRYPTO_EddsaSignature sig; struct MAJ_Specification spec[] = { MAJ_spec_fixed_auto ("master_sig", &sig), MAJ_spec_absolute_time ("stamp_expire_deposit", &deposit_valid_until), MAJ_spec_absolute_time ("stamp_expire_withdraw", &withdraw_valid_until), MAJ_spec_absolute_time ("stamp_start", &valid_from), MAJ_spec_absolute_time ("stamp_expire_legal", &expire_legal), MAJ_spec_amount ("value", &value), MAJ_spec_amount ("fee_withdraw", &fee_withdraw), MAJ_spec_amount ("fee_deposit", &fee_deposit), MAJ_spec_amount ("fee_refresh", &fee_refresh), MAJ_spec_rsa_public_key ("denom_pub", &pk), MAJ_spec_end }; if (GNUNET_OK != MAJ_parse_json (denom_key_obj, spec)) { GNUNET_break_op (0); return GNUNET_SYSERR; } memset (&denom_key_issue, 0, sizeof (denom_key_issue)); GNUNET_CRYPTO_rsa_public_key_hash (pk, &denom_key_issue.denom_hash); denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); denom_key_issue.purpose.size = htonl (sizeof (struct TALER_DenominationKeyValidityPS) - offsetof (struct TALER_DenominationKeyValidityPS, purpose)); denom_key_issue.master = *master_key; denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until); denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until); denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (expire_legal); TALER_amount_hton (&denom_key_issue.value, &value); TALER_amount_hton (&denom_key_issue.fee_withdraw, &fee_withdraw); TALER_amount_hton (&denom_key_issue.fee_deposit, &fee_deposit); TALER_amount_hton (&denom_key_issue.fee_refresh, &fee_refresh); EXITIF (GNUNET_SYSERR == GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, &denom_key_issue.purpose, &sig, &master_key->eddsa_pub)); GNUNET_CRYPTO_hash_context_read (hash_context, &denom_key_issue.denom_hash, sizeof (struct GNUNET_HashCode)); denom_key->key.rsa_public_key = pk; denom_key->valid_from = valid_from; denom_key->withdraw_valid_until = withdraw_valid_until; denom_key->deposit_valid_until = deposit_valid_until; denom_key->value = value; denom_key->fee_withdraw = fee_withdraw; denom_key->fee_deposit = fee_deposit; denom_key->fee_refresh = fee_refresh; return GNUNET_OK; EXITIF_exit: MAJ_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[out] key_data where to store the results we decoded * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON) */ static int decode_keys_json (json_t *resp_obj, struct TALER_MINT_Keys *key_data) { struct GNUNET_TIME_Absolute list_issue_date; struct TALER_MintSignatureP sig; struct TALER_MintKeySetPS ks; struct GNUNET_HashContext *hash_context; const struct TALER_MintPublicKeyP *pub; if (JSON_OBJECT != json_typeof (resp_obj)) return GNUNET_SYSERR; hash_context = GNUNET_CRYPTO_hash_context_start (); /* parse the master public key and issue date of the response */ { struct MAJ_Specification spec[] = { MAJ_spec_fixed_auto ("master_public_key", &key_data->master_pub), MAJ_spec_fixed_auto ("eddsa_sig", &sig), MAJ_spec_absolute_time ("list_issue_date", &list_issue_date), MAJ_spec_end }; EXITIF (GNUNET_OK != MAJ_parse_json (resp_obj, spec)); } /* 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_malloc (sizeof (struct TALER_MINT_SigningPublicKey) * key_data->num_sign_keys); index = 0; json_array_foreach (sign_keys_array, index, sign_key_obj) { EXITIF (GNUNET_SYSERR == parse_json_signkey (&key_data->sign_keys[index], sign_key_obj, &key_data->master_pub)); } } /* parse the denomination keys */ { 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)); EXITIF (0 == (key_data->num_denom_keys = json_array_size (denom_keys_array))); key_data->denom_keys = GNUNET_malloc (sizeof (struct TALER_MINT_DenomPublicKey) * key_data->num_denom_keys); index = 0; json_array_foreach (denom_keys_array, index, denom_key_obj) { EXITIF (GNUNET_SYSERR == parse_json_denomkey (&key_data->denom_keys[index], denom_key_obj, &key_data->master_pub, hash_context)); } } return GNUNET_OK; /* FIXME: parse the auditor keys (#3847) */ /* Validate signature... */ ks.purpose.size = htonl (sizeof (ks)); ks.purpose.purpose = htonl (TALER_SIGNATURE_MINT_KEY_SET); ks.list_issue_date = GNUNET_TIME_absolute_hton (list_issue_date); GNUNET_CRYPTO_hash_context_finish (hash_context, &ks.hc); hash_context = NULL; pub = TALER_MINT_get_signing_key (key_data); EXITIF (NULL == pub); EXITIF (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_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; } /** * We have successfully received the reply to the /keys * request from the mint. We now need to parse the reply * and, if successful, store the resulting information * in the `key_data` structure. * * @param kr key request with all of the data to parse * and references to the `struct TALER_MINT_Handle` * where we need to store the result * @return #GNUNET_OK on success, * #GNUNET_SYSERR on failure */ static int parse_response_keys_get (struct KeysRequest *kr) { json_t *resp_obj; json_error_t error; int ret; resp_obj = json_loadb (kr->buf, kr->buf_size, JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); if (NULL == resp_obj) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to parse received /keys data as JSON object\n"); GNUNET_free_non_null (kr->buf); kr->buf = NULL; kr->buf_size = 0; return GNUNET_SYSERR; } GNUNET_free_non_null (kr->buf); kr->buf = NULL; kr->buf_size = 0; ret = decode_keys_json (resp_obj, &kr->mint->key_data); json_decref (resp_obj); return ret; } /** * Callback used when downloading the reply to a /keys request * is complete. * * @param cls the `struct KeysRequest` * @param eh easy handle of the original request */ static void keys_completed_cb (void *cls, CURL *eh) { struct KeysRequest *kr = cls; struct TALER_MINT_Handle *mint = kr->mint; long response_code; /* FIXME: might want to check response code? */ if (CURLE_OK != curl_easy_getinfo (eh, CURLINFO_RESPONSE_CODE, &response_code)) { /* unexpected error... */ GNUNET_break (0); response_code = 0; } switch (response_code) { case 0: kr->eno = 1; break; case MHD_HTTP_OK: break; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Mint returned status code %u for /keys\n", response_code); kr->eno = 1; break; } if ( (0 != kr->eno) || (GNUNET_OK != parse_response_keys_get (kr)) ) { mint->kr = NULL; free_keys_request (kr); mint->state = MHS_FAILED; /* notify application that we failed */ if (NULL != mint->cert_cb) { mint->cert_cb (mint->cert_cb_cls, NULL); mint->cert_cb = NULL; } return; } mint->kr = NULL; free_keys_request (kr); mint->state = MHS_CERT; /* notify application about the key information */ if (NULL != mint->cert_cb) { mint->cert_cb (mint->cert_cb_cls, &mint->key_data); mint->cert_cb = NULL; } } /* ********************* library internal API ********* */ /** * Get the context of a mint. * * @param h the mint handle to query * @return ctx context to execute jobs in */ struct TALER_MINT_Context * MAH_handle_to_context (struct TALER_MINT_Handle *h) { return h->ctx; } /** * Check if the handle is ready to process requests. * * @param h the mint handle to query * @return #GNUNET_YES if we are ready, #GNUNET_NO if not */ int MAH_handle_is_ready (struct TALER_MINT_Handle *h) { return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; } /** * Obtain the URL to use for an API request. * * @param h the mint handle to query * @param path Taler API path (i.e. "/withdraw/sign") * @return the full URI to use with cURL */ char * MAH_path_to_url (struct TALER_MINT_Handle *h, const char *path) { char *url; GNUNET_asprintf (&url, "%s%s", h->url, path); return url; } /* ********************* public API ******************* */ /** * Initialise a connection to the mint. Will connect to the * mint and obtain information about the mint's master public * key and the mint's auditor. The respective information will * be passed to the @a cert_cb once available, and all future * interactions with the mint will be checked to be signed * (where appropriate) by the respective master key. * * @param ctx the context * @param url HTTP base URL for the mint * @param cert_cb function to call with the mint's certification information * @param cert_cb_cls closure for @a cert_cb * @param ... list of additional arguments, terminated by #TALER_MINT_OPTION_END. * @return the mint handle; NULL upon error */ struct TALER_MINT_Handle * TALER_MINT_connect (struct TALER_MINT_Context *ctx, const char *url, TALER_MINT_CertificationCallback cert_cb, void *cert_cb_cls, ...) { struct TALER_MINT_Handle *mint; struct KeysRequest *kr; CURL *c; mint = GNUNET_new (struct TALER_MINT_Handle); mint->ctx = ctx; mint->url = GNUNET_strdup (url); mint->cert_cb = cert_cb; mint->cert_cb_cls = cert_cb_cls; kr = GNUNET_new (struct KeysRequest); kr->mint = mint; kr->url = MAH_path_to_url (mint, "/keys"); c = curl_easy_init (); GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_URL, kr->url)); GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_ERRORBUFFER, kr->emsg)); GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &keys_download_cb)); GNUNET_assert (CURLE_OK == curl_easy_setopt (c, CURLOPT_WRITEDATA, kr)); kr->job = MAC_job_add (mint->ctx, c, &keys_completed_cb, kr); mint->kr = kr; return mint; } /** * Disconnect from the mint * * @param mint the mint handle */ void TALER_MINT_disconnect (struct TALER_MINT_Handle *mint) { if (NULL != mint->kr) { MAC_job_cancel (mint->kr->job); free_keys_request (mint->kr); mint->kr = NULL; } GNUNET_array_grow (mint->key_data.sign_keys, mint->key_data.num_sign_keys, 0); GNUNET_array_grow (mint->key_data.denom_keys, mint->key_data.num_denom_keys, 0); GNUNET_array_grow (mint->key_data.auditors, mint->key_data.num_auditors, 0); GNUNET_free (mint->url); GNUNET_free (mint); } /** * Obtain the current signing key from the mint. * * @param keys the mint's key set * @return sk current online signing key for the mint, NULL on error */ const struct TALER_MintPublicKeyP * TALER_MINT_get_signing_key (const struct TALER_MINT_Keys *keys) { struct GNUNET_TIME_Absolute now; unsigned int i; now = GNUNET_TIME_absolute_get (); for (i=0;inum_sign_keys;i++) if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us) && (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us) ) return &keys->sign_keys[i].key; return NULL; } /** * Obtain the denomination key details from the mint. * * @param keys the mint's key set * @param pk public key of the denomination to lookup * @return details about the given denomination key */ const struct TALER_MINT_DenomPublicKey * TALER_MINT_get_denomination_key (const struct TALER_MINT_Keys *keys, const struct TALER_DenominationPublicKey *pk) { unsigned int i; for (i=0;inum_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 keys from the mint. * * @param mint the mint handle * @return the mint's key set */ const struct TALER_MINT_Keys * TALER_MINT_get_keys (const struct TALER_MINT_Handle *mint) { return &mint->key_data; } /* end of mint_api_handle.c */