summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Blättler <blatc2@bfh.ch>2024-04-21 16:05:20 +0200
committerChristian Blättler <blatc2@bfh.ch>2024-04-21 16:05:20 +0200
commitfa4322c49ca1d768bb9c6bb75436fab89d75e623 (patch)
tree7f9d39c04b4178f62bc5045c1bb1d9da4cdda744
parente3965464791470843660ccf2f54fced53ffffcdf (diff)
downloadmerchant-fa4322c49ca1d768bb9c6bb75436fab89d75e623.tar.gz
merchant-fa4322c49ca1d768bb9c6bb75436fab89d75e623.tar.bz2
merchant-fa4322c49ca1d768bb9c6bb75436fab89d75e623.zip
work on pay handler
-rw-r--r--src/backend/taler-merchant-httpd_contract.c150
-rw-r--r--src/backend/taler-merchant-httpd_contract.h19
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c195
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c30
4 files changed, 375 insertions, 19 deletions
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
index 60f45776..6e98f56f 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -19,7 +19,9 @@
* @author Christian Blättler
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
#include <jansson.h>
+#include <stdint.h>
#include "taler-merchant-httpd_contract.h"
enum TALER_MerchantContractInputType
@@ -83,7 +85,7 @@ TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t)
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
static enum GNUNET_GenericReturnValue
-parse_choice (void *cls,
+parse_choices (void *cls,
json_t *root,
struct GNUNET_JSON_Specification *spec)
{
@@ -263,10 +265,154 @@ TALER_JSON_spec_choices (const char *name,
{
struct GNUNET_JSON_Specification ret = {
.cls = (void *) choices_len,
- .parser = &parse_choice,
+ .parser = &parse_choices,
.field = name,
.ptr = choices,
};
return ret;
+}
+
+
+/**
+ * Parse given JSON object to token families array.
+ *
+ * @param cls closure, pointer to array length
+ * @param root the json object representing the token families. The keys are
+ * the token family slugs.
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_token_families (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_MerchantContractTokenFamily **families = spec->ptr;
+ unsigned int *families_len = cls;
+ json_t *jfamily;
+ const char *slug;
+ if (!json_is_object (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ json_object_foreach(root, slug, jfamily)
+ {
+ const json_t *keys;
+ struct TALER_MerchantContractTokenFamily family = {
+ .slug = slug
+ };
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("keys",
+ &keys),
+ GNUNET_JSON_spec_bool ("critical",
+ &family.critical),
+ /* TODO: Figure out if these fields should be 'const' */
+ // GNUNET_JSON_spec_string ("description",
+ // &family.description),
+ // GNUNET_JSON_spec_object_const ("description_i18n",
+ // &family.description_i18n),
+ };
+ const char *error_name;
+ unsigned int error_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jfamily,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (family.keys,
+ family.keys_len,
+ json_array_size (keys));
+
+ for (unsigned int i = 0; i<family.keys_len; i++)
+ {
+ /* TODO: Move this to TALER_JSON_spec_token_issue_key */
+ int64_t cipher;
+ struct TALER_MerchantContractTokenFamilyKey key;
+ key = family.keys[i];
+ /* TODO: Free when not used anymore */
+ key.pub.public_key = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ struct GNUNET_JSON_Specification key_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_pub",
+ &key.pub.public_key->pub_key_hash),
+ GNUNET_JSON_spec_rsa_public_key ("rsa_pub",
+ &key.pub.public_key->details.rsa_public_key),
+ // GNUNET_JSON_spec_fixed_auto ("cs_pub",
+ // &key.pub.public_key->details.cs_public_key)),
+ GNUNET_JSON_spec_int64 ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &key.valid_after),
+ GNUNET_JSON_spec_timestamp ("valid_before",
+ &key.valid_before),
+ GNUNET_JSON_spec_end()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (keys, i),
+ key_spec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ key_spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ switch (cipher) {
+ case GNUNET_CRYPTO_BSA_RSA:
+ key.pub.public_key->cipher = GNUNET_CRYPTO_BSA_RSA;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key.pub.public_key->cipher = GNUNET_CRYPTO_BSA_CS;
+ break;
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'cipher' invalid in key #%u\n",
+ i);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_array_append (*families, *families_len, family);
+ }
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_token_families (const char *name,
+ struct TALER_MerchantContractTokenFamily **families,
+ unsigned int *families_len)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) families_len,
+ .parser = &parse_token_families,
+ .field = name,
+ .ptr = families,
+ };
+
+ return ret;
} \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
index ee77b4c1..476fdcc0 100644
--- a/src/backend/taler-merchant-httpd_contract.h
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -280,6 +280,11 @@ struct TALER_MerchantContractTokenFamily
const char *slug;
/**
+ * Human-readable name of the token family.
+ */
+ char *name;
+
+ /**
* Human-readable description of the semantics of the tokens issued by
* this token family.
*/
@@ -605,3 +610,17 @@ struct GNUNET_JSON_Specification
TALER_JSON_spec_choices (const char *name,
struct TALER_MerchantContractChoice **choices,
unsigned int *choices_len);
+
+/**
+ * Provide specification to parse given JSON object to an array
+ * of token families.
+ *
+ * @param name name of the token families field in the JSON
+ * @param[out] families pointer to the first element of the array
+ * @param[out] families_len pointer to the length of the array
+ * @return spec for parsing a token families object
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_token_families (const char *name,
+ struct TALER_MerchantContractTokenFamily **families,
+ unsigned int *families_len);
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 5532f1ab..27b42b63 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -25,10 +25,15 @@
* @author Florian Dold
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <stddef.h>
#include <stdint.h>
+#include <string.h>
#include <taler/taler_dbevents.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
@@ -84,6 +89,11 @@ enum PayPhase
PP_CHECK_CONTRACT,
/**
+ * Validate provided tokens and token evelopes.
+ */
+ PP_VALIDATE_TOKENS,
+
+ /**
* Contract has been paid.
*/
PP_CONTRACT_PAID,
@@ -319,11 +329,16 @@ struct PayContext
struct TokenUseConfirmation *tokens;
/**
- * Array with @e choices_cnt choices from the contract terms.
+ * Array with @e choices_len choices from the contract terms.
*/
struct TALER_MerchantContractChoice *choices;
/**
+ * Array with @e token_families_len token families from the contract terms.
+ */
+ struct TALER_MerchantContractTokenFamily *token_families;
+
+ /**
* MHD connection to return to
*/
struct MHD_Connection *connection;
@@ -487,6 +502,11 @@ struct PayContext
unsigned int choices_len;
/**
+ * Length of the @e token_families array.
+ */
+ unsigned int token_families_len;
+
+ /**
* Number of exchanges involved in the payment. Length
* of the @e eg array.
*/
@@ -2092,6 +2112,149 @@ phase_execute_pay_transaction (struct PayContext *pc)
/**
+ * Validate tokens and token envelopes. First, we check if all tokens listed in
+ * the 'inputs' array of the selected choice are present in the 'tokens' array
+ * of the request. Then, we validate the signatures of each provided token.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_validate_tokens (struct PayContext *pc)
+{
+ if (NULL == pc->choices || 0 <= pc->choices_len)
+ {
+ /* No tokens to validate */
+ pc->phase = PP_PAY_TRANSACTION;
+ return;
+ }
+
+ if (pc->choice_index < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has non-empty choices array but"
+ "request is missing 'choice_index' field\n",
+ pc->order_id);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISSING,
+ NULL));
+ return;
+ }
+
+ if (pc->choice_index >= pc->choices_len)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has choices array with %u elements but"
+ "request has 'choice_index' field with value %ld\n",
+ pc->order_id,
+ pc->choices_len,
+ pc->choice_index);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_OUT_OF_BOUNDS,
+ NULL));
+ return;
+ }
+
+ {
+ struct TALER_MerchantContractChoice selected;
+
+ selected = pc->choices[pc->choice_index];
+
+ /* 1. Iterate over inputs of selected choice:
+ 1.1. Get public key for each input lookup_token_key (slug, valid_after).
+ 1.2. Iterate over provided tokens and check if required number with matching h_issue are present.
+ 1.3. Validate ub_sig with the issue public key, validate token_sig using the token_pub key of the request.
+ 1.4. Sum up validated tokens and check if validated_len == tokens_cnt after loop. */
+ for (unsigned int i = 0; i < selected.inputs_len; i++)
+ {
+ unsigned int num_validated = 0;
+ struct TALER_MerchantContractInput input = selected.inputs[i];
+
+ if (input.type != TALER_MCIT_COIN)
+ {
+ /* only validate coins, for now */
+ continue;
+ }
+
+ struct TALER_MerchantContractTokenFamily *family = NULL;
+ struct TALER_MerchantContractTokenFamilyKey *key = NULL;
+ for (unsigned int j = 0; j < pc->token_families_len; j++)
+ {
+ if (0 != strcmp (pc->token_families[j].slug, input.details.token.token_family_slug))
+ {
+ continue;
+ }
+ family = &pc->token_families[j];
+ for (unsigned int k = 0; k < family->keys_len; k++)
+ {
+ if (GNUNET_TIME_timestamp_cmp(family->keys[k].valid_after,
+ ==,
+ input.details.token.valid_after))
+ {
+ key = &family->keys[k];
+ break;
+ }
+ }
+ break;
+ }
+
+ if (NULL == family || NULL == key)
+ {
+ /* this should never happen, since the choices & token families are validated on insert. */
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL));
+ return;
+ }
+
+ for (size_t j = 0; j < pc->tokens_cnt; j++)
+ {
+ if (0 != GNUNET_CRYPTO_hash_cmp (&pc->tokens[j].h_issue.hash, &key->pub.public_key->pub_key_hash))
+ {
+ continue;
+ }
+
+ /* TODO: Design data structures for signature and implement it. */
+ if (GNUNET_OK != TALER_merchant_token_issue_verify ())
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ISSUE_SIG_INVALID,
+ NULL));
+ return;
+ }
+
+ num_validated++;
+ }
+
+ if (num_validated != input.details.token.count)
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_COUNT_MISMATCH,
+ NULL));
+ return;
+ }
+ }
+ }
+
+ pc->phase = PP_PAY_TRANSACTION;
+}
+
+
+/**
* Function called with information about a coin that was deposited.
* Checks if this coin is in our list of deposits as well.
*
@@ -2158,6 +2321,7 @@ phase_contract_paid (struct PayContext *pc)
pc->order_serial,
&deposit_paid_check,
pc);
+ /* TODO: Check tokens */
if (qs <= 0)
{
GNUNET_break (0);
@@ -2359,6 +2523,11 @@ phase_check_contract (struct PayContext *pc)
&pc->choices,
&pc->choices_len),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_token_families ("token_families",
+ &pc->token_families,
+ &pc->token_families_len),
+ NULL),
GNUNET_JSON_spec_fixed_auto ("h_wire",
&pc->h_wire),
GNUNET_JSON_spec_mark_optional (
@@ -2461,7 +2630,7 @@ phase_check_contract (struct PayContext *pc)
}
pc->wm = wm;
}
- pc->phase = PP_PAY_TRANSACTION;
+ pc->phase = PP_VALIDATE_TOKENS;
}
@@ -2477,6 +2646,7 @@ phase_parse_pay (struct PayContext *pc)
const char *session_id = NULL;
const json_t *coins;
const json_t *tokens;
+ bool choice_index_missing = false;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("coins",
&coins),
@@ -2487,7 +2657,7 @@ phase_parse_pay (struct PayContext *pc)
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_int64 ("choice_index",
&pc->choice_index),
- NULL),
+ &choice_index_missing),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("tokens",
&tokens),
@@ -2523,6 +2693,11 @@ phase_parse_pay (struct PayContext *pc)
/* use empty string as default if client didn't specify it */
pc->session_id = GNUNET_strdup ("");
}
+ /* set choice_index to -1 to indicate it was not provided */
+ if (choice_index_missing)
+ {
+ pc->choice_index = -1;
+ }
pc->coins_cnt = json_array_size (coins);
if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
{
@@ -2704,6 +2879,17 @@ phase_parse_pay (struct PayContext *pc)
: MHD_NO);
return;
}
+ /* TODO: Design data structures for signature and implement it. */
+ if (GNUNET_OK != TALER_wallet_token_use_verify ())
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_USE_SIG_INVALID,
+ "invalid token signature"));
+ return;
+ }
for (unsigned int j = 0; j<tokens_index; j++)
{
if (0 ==
@@ -2813,6 +2999,9 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
case PP_CONTRACT_PAID:
phase_contract_paid (pc);
break;
+ case PP_VALIDATE_TOKENS:
+ phase_validate_tokens (pc);
+ break;
case PP_PAY_TRANSACTION:
phase_execute_pay_transaction (pc);
break;
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index 5473dec4..704c3823 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -1608,11 +1608,11 @@ set_token_family (struct OrderContext *oc,
}
family->slug = slug;
+ family->name = key_details.token_family.name;
family->description = key_details.token_family.description;
family->description_i18n = key_details.token_family.description_i18n;
GNUNET_free (key_details.token_family.slug);
- GNUNET_free (key_details.token_family.name);
switch (key_details.token_family.kind) {
case TALER_MERCHANTDB_TFK_Subscription:
@@ -1647,7 +1647,7 @@ serialize_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
json_t *merchant;
- json_t *token_types = json_object ();
+ json_t *token_families = json_object ();
json_t *choices = json_array ();
merchant = GNUNET_JSON_PACK (
@@ -1697,13 +1697,11 @@ serialize_order (struct OrderContext *oc)
for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
{
json_t *keys = json_array();
- enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher = GNUNET_CRYPTO_BSA_INVALID;
struct TALER_MerchantContractTokenFamily *family = &oc->parse_choices.token_families[i];
for (unsigned int j = 0; j<family->keys_len; j++)
{
struct TALER_MerchantContractTokenFamilyKey key = family->keys[j];
- cipher = key.pub.public_key->cipher;
json_t *jkey = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h_pub",
@@ -1713,7 +1711,9 @@ serialize_order (struct OrderContext *oc)
key.pub.public_key->details.rsa_public_key)),
// GNUNET_JSON_pack_allow_null(
// GNUNET_JSON_pack_data_auto ("cs_pub",
- // &key.pub.public_key.details.cs_public_key)),
+ // &key.pub.public_key->details.cs_public_key)),
+ GNUNET_JSON_pack_int64 ("cipher",
+ key.pub.public_key->cipher),
GNUNET_JSON_pack_timestamp ("valid_after",
key.valid_after),
GNUNET_JSON_pack_timestamp ("valid_before",
@@ -1723,22 +1723,24 @@ serialize_order (struct OrderContext *oc)
GNUNET_assert (0 == json_array_append_new (keys, jkey));
}
+ /* TODO: Add 'details' field. */
json_t *jfamily = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ family->name),
GNUNET_JSON_pack_string ("description",
family->description),
- GNUNET_JSON_pack_int64 ("cipher",
- cipher),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("description_i18n",
family->description_i18n)),
GNUNET_JSON_pack_array_steal ("keys",
- keys)
+ keys),
+ GNUNET_JSON_pack_bool ("critical",
+ family->critical)
);
- GNUNET_assert (0 ==
- json_object_set_new (token_types,
- family->slug,
- jfamily));
+ GNUNET_assert (0 == json_object_set_new (token_families,
+ family->slug,
+ jfamily));
}
for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
@@ -1860,8 +1862,8 @@ serialize_order (struct OrderContext *oc)
choices)
),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("token_types",
- token_types)
+ GNUNET_JSON_pack_object_incref ("token_families",
+ token_families)
),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",