diff options
author | Christian Blättler <blatc2@bfh.ch> | 2024-04-21 16:05:20 +0200 |
---|---|---|
committer | Christian Blättler <blatc2@bfh.ch> | 2024-04-21 16:05:20 +0200 |
commit | fa4322c49ca1d768bb9c6bb75436fab89d75e623 (patch) | |
tree | 7f9d39c04b4178f62bc5045c1bb1d9da4cdda744 | |
parent | e3965464791470843660ccf2f54fced53ffffcdf (diff) | |
download | merchant-fa4322c49ca1d768bb9c6bb75436fab89d75e623.tar.gz merchant-fa4322c49ca1d768bb9c6bb75436fab89d75e623.tar.bz2 merchant-fa4322c49ca1d768bb9c6bb75436fab89d75e623.zip |
work on pay handler
-rw-r--r-- | src/backend/taler-merchant-httpd_contract.c | 150 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_contract.h | 19 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-pay.c | 195 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 30 |
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", |