aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Blättler <blatc2@bfh.ch>2024-04-27 16:47:34 +0200
committerChristian Blättler <blatc2@bfh.ch>2024-04-27 16:47:34 +0200
commit5d9db0012144cbad9ba90b654fdfff17b55b6af9 (patch)
tree2ff02fb72aab11ec3c1b332ddfb59e3e4d8a2e64
parent27ab65168f8c5024a1ad4220e7483a2b8ceeb5bb (diff)
validate and sign token envelopes
-rw-r--r--src/backend/taler-merchant-httpd_contract.h10
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c426
2 files changed, 362 insertions, 74 deletions
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
index 0e52b6e2..6c1841e9 100644
--- a/src/backend/taler-merchant-httpd_contract.h
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -326,16 +326,6 @@ struct TALER_MerchantContractTokenFamily
*/
struct
{
- // /**
- // * When does the subscription period start?
- // */
- // struct GNUNET_TIME_Timestamp start_date;
-
- // /**
- // * When does the subscription period end?
- // */
- // struct GNUNET_TIME_Timestamp end_date;
-
/**
* Array of domain names where this subscription can be safely used
* (e.g. the issuer warrants that these sites will re-issue tokens of
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 25661a4e..1808989a 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -26,6 +26,7 @@
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
@@ -42,6 +43,7 @@
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -56,9 +58,15 @@
/**
* TODO: What is a good value for this?
- * Maximum number of tokens that we allow per transaction
+ * Maximum number of tokens that we allow as inputs per transaction
*/
-#define MAX_TOKEN_ALLOWED_TOKENS 128
+#define MAX_TOKEN_ALLOWED_INPUT_TOKENS 128
+
+/**
+ * TODO: What is a good value for this?
+ * Maximum number of tokens that we allow as outputs per transaction
+ */
+#define MAX_TOKEN_ALLOWED_OUTPUT_TOKENS 128
/**
* How often do we ask the exchange again about our
@@ -260,6 +268,25 @@ struct TokenUseConfirmation
/**
+ * Information about a token envelope.
+ */
+struct TokenEnvelope
+{
+
+ /**
+ * Blinded token use public keys waiting to be signed.
+ */
+ struct TALER_TokenEnvelopeP blinded_token;
+
+ /**
+ * Hash of token issue public key.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+};
+
+
+/**
* Information kept during a pay request for each exchange.
*/
struct ExchangeGroup
@@ -330,11 +357,22 @@ struct PayContext
struct DepositConfirmation *dc;
/**
- * Array with @e tokens_cnt tokens we are using.
+ * Array with @e tokens_cnt input tokens passed to this request.
*/
struct TokenUseConfirmation *tokens;
/**
+ * Array with @e output_tokens_cnt signed tokens returned in
+ * the response to the wallet.
+ */
+ struct TALER_TokenIssueBlindSignatureP *output_tokens;
+
+ /**
+ * Array with @e token_envelopes_cnt (blinded) token envelopes.
+ */
+ struct TokenEnvelope *token_envelopes;
+
+ /**
* Array with @e choices_len choices from the contract terms.
*/
struct TALER_MerchantContractChoice *choices;
@@ -513,12 +551,24 @@ struct PayContext
size_t coins_cnt;
/**
- * Number of tokens this payment uses. Length
+ * Number of input tokens passed to this request. Length
* of the @e tokens array.
*/
size_t tokens_cnt;
/**
+ * Number of token envelopes passed to this request.
+ * Length of the @e token_envelopes array.
+ */
+ size_t token_envelopes_cnt;
+
+ /**
+ * Number of output tokens to return in the response.
+ * Length of the @e output_tokens array.
+ */
+ unsigned int output_tokens_len;
+
+ /**
* Length of the @e choices array.
*/
unsigned int choices_len;
@@ -2077,6 +2127,8 @@ phase_execute_pay_transaction (struct PayContext *pc)
}
}
+ /* TODO: Store signed output tokens in database. */
+
TMH_notify_order_change (hc->instance,
TMH_OSF_CLAIMED | TMH_OSF_PAID,
pc->timestamp,
@@ -2135,6 +2187,186 @@ phase_execute_pay_transaction (struct PayContext *pc)
/**
+ * Ensures that the expected number of tokens for a @e key
+ * are provided as inputs and have valid signatures.
+ */
+static enum GNUNET_GenericReturnValue
+find_valid_input_tokens (struct PayContext *pc,
+ struct TALER_MerchantContractTokenFamilyKey *key,
+ unsigned int expected_num)
+{
+ int num_validated = 0;
+ struct TokenUseConfirmation *tuc = NULL;
+
+ for (size_t i = 0; i < pc->tokens_cnt; i++)
+ {
+ if (0 != GNUNET_CRYPTO_hash_cmp (&pc->tokens[i].h_issue.hash,
+ &key->pub.public_key->pub_key_hash))
+ {
+ continue;
+ }
+
+ tuc = &pc->tokens[i];
+ if (NULL == tuc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with "
+ "valid_after `%s' not found\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'tokens' array is missing "
+ "required input token"));
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK != TALER_merchant_token_issue_verify (&tuc->pub,
+ &key->pub,
+ &tuc->unblinded_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with valid_after "
+ "`%s' is invalid\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ 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 GNUNET_NO;
+ }
+
+ if (GNUNET_OK != TALER_wallet_token_use_verify (&pc->h_contract_terms,
+ &pc->h_wallet_data,
+ &tuc->pub,
+ &tuc->sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with valid_after "
+ "`%s' has invalid use signature\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ 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_USE_SIG_INVALID,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ num_validated++;
+ }
+
+ if (num_validated != expected_num)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected %d tokens for public key with valid_after "
+ "`%s', but found %d\n",
+ expected_num,
+ GNUNET_TIME_timestamp2s (key->valid_after),
+ num_validated);
+ 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 GNUNET_NO;
+ }
+
+ return GNUNET_YES;
+}
+
+
+static enum GNUNET_GenericReturnValue
+sign_token_envelopes (struct PayContext *pc,
+ const char *token_family_slug,
+ struct TALER_MerchantContractTokenFamilyKey *key,
+ struct TALER_TokenIssuePrivateKeyP *priv,
+ unsigned int expected_num)
+{
+ int num_signed = 0;
+
+ for (unsigned int i = 0; i<pc->token_envelopes_cnt; i++)
+ {
+ if (0 != GNUNET_CRYPTO_hash_cmp (&pc->token_envelopes[i].h_issue.hash,
+ &key->pub.public_key->pub_key_hash))
+ {
+ continue;
+ }
+
+ TALER_merchant_token_issue_sign (&pc->token_envelopes[i].blinded_token,
+ priv,
+ &pc->output_tokens[i]);
+
+ num_signed++;
+ }
+
+ if (num_signed != expected_num)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected %d token envelopes for public key with valid_after "
+ "`%s', but found %d\n",
+ expected_num,
+ GNUNET_TIME_timestamp2s (key->valid_after),
+ num_signed);
+ 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_ENVELOPE_COUNT_MISMATCH,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Find metching token family based on @a slug. Then use @a valid_after to
+ * find the matching public key.
+ *
+ * @param pc payment context
+ * @param slug token family slug
+ * @param valid_after valid_after timestamp
+ * @param[out] family matching token family
+ * @param[out] key matching public key
+ */
+static void
+lookup_token_family_key (struct PayContext *pc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct TALER_MerchantContractTokenFamily *family,
+ struct TALER_MerchantContractTokenFamilyKey *key)
+{
+ for (unsigned int i = 0; i < pc->token_families_len; i++)
+ {
+ if (0 != strcmp (pc->token_families[i].slug, slug))
+ {
+ continue;
+ }
+ *family = pc->token_families[i];
+ for (unsigned int k = 0; k < family->keys_len; k++)
+ {
+ if (GNUNET_TIME_timestamp_cmp(family->keys[k].valid_after,
+ ==,
+ valid_after))
+ {
+ *key = family->keys[k];
+ break;
+ }
+ }
+ break;
+ }
+}
+
+
+/**
* 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.
@@ -2193,42 +2425,28 @@ phase_validate_tokens (struct PayContext *pc)
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++)
+ for (unsigned int i = 0; i<selected.inputs_len; i++)
{
- unsigned int num_validated = 0;
struct TALER_MerchantContractInput input = selected.inputs[i];
+ struct TALER_MerchantContractTokenFamily *family = NULL;
+ struct TALER_MerchantContractTokenFamilyKey *key = NULL;
if (input.type != TALER_MCIT_TOKEN)
{
- /* only validate tokens, for now */
+ /* only validate inputs of type token (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;
- }
+ lookup_token_family_key (pc,
+ input.details.token.token_family_slug,
+ input.details.token.valid_after,
+ family,
+ key);
if (NULL == family || NULL == key)
{
- /* this should never happen, since the choices & token families are validated on insert. */
+ /* this should never happen, since the choices and
+ token families are validated on insert. */
GNUNET_break (0);
pay_end (pc,
TALER_MHD_reply_with_error (pc->connection,
@@ -2238,53 +2456,80 @@ phase_validate_tokens (struct PayContext *pc)
return;
}
- for (size_t j = 0; j < pc->tokens_cnt; j++)
+ if (GNUNET_NO == find_valid_input_tokens (pc,
+ key,
+ input.details.token.count))
{
- if (0 != GNUNET_CRYPTO_hash_cmp (&pc->tokens[j].h_issue.hash, &key->pub.public_key->pub_key_hash))
- {
- continue;
- }
+ /* Error is already scheduled from find_valid_input_token. */
+ return;
+ }
+ }
- if (GNUNET_OK != TALER_merchant_token_issue_verify (&pc->tokens[j].pub,
- &key->pub,
- &pc->tokens[j].unblinded_sig))
- {
- 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;
- }
+ GNUNET_array_grow (pc->output_tokens,
+ pc->output_tokens_len,
+ selected.outputs_len);
- if (GNUNET_OK != TALER_wallet_token_use_verify (&pc->h_contract_terms,
- &pc->h_wallet_data,
- &pc->tokens[j].pub,
- &pc->tokens[j].sig))
- {
- 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_USE_SIG_INVALID,
- NULL));
- return;
- }
+ for (unsigned int i = 0; i<selected.outputs_len; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
+ struct TALER_MerchantContractOutput output = selected.outputs[i];
+ struct TALER_MerchantContractTokenFamily *family = NULL;
+ struct TALER_MerchantContractTokenFamilyKey *key = NULL;
- num_validated++;
+ if (output.type != TALER_MCOT_TOKEN)
+ {
+ /* only validate outputs of type tokens (for now) */
+ continue;
}
- if (num_validated != input.details.token.count)
+ lookup_token_family_key (pc,
+ output.details.token.token_family_slug,
+ output.details.token.valid_after,
+ family,
+ key);
+
+ if (NULL == family || NULL == key)
{
+ /* this should never happen, since the choices and
+ token families are validated on insert. */
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,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
NULL));
return;
}
+
+ qs = TMH_db->lookup_token_family_key (TMH_db->cls,
+ pc->hc->instance->settings.id,
+ family->slug,
+ key->valid_after,
+ key->valid_after,
+ &details);
+
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL));
+ return;
+ }
+
+ if (GNUNET_OK != sign_token_envelopes (pc,
+ family->slug,
+ key,
+ &details.priv,
+ output.details.token.count))
+ {
+ /* Error is already scheduled from sign_token_envelopes. */
+ return;
+ }
+
}
}
@@ -2736,6 +2981,7 @@ phase_parse_pay (struct PayContext *pc)
const char *session_id = NULL;
const json_t *coins;
const json_t *tokens;
+ const json_t *tokens_evs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("coins",
&coins),
@@ -2751,6 +2997,10 @@ phase_parse_pay (struct PayContext *pc)
GNUNET_JSON_spec_array_const ("tokens",
&tokens),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("tokens_evs",
+ &tokens_evs),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -2796,7 +3046,7 @@ phase_parse_pay (struct PayContext *pc)
}
pc->tokens_cnt = json_array_size (tokens);
- if (pc->tokens_cnt > MAX_TOKEN_ALLOWED_TOKENS)
+ if (pc->tokens_cnt > MAX_TOKEN_ALLOWED_INPUT_TOKENS)
{
GNUNET_break_op (0);
pay_end (pc,
@@ -2815,6 +3065,9 @@ phase_parse_pay (struct PayContext *pc)
pc->tokens = GNUNET_new_array (pc->tokens_cnt,
struct TokenUseConfirmation);
+ pc->token_envelopes = GNUNET_new_array (pc->token_envelopes_cnt,
+ struct TokenEnvelope);
+
/* This loop populates the array 'dc' in 'pc' */
{
unsigned int coins_index;
@@ -2981,6 +3234,51 @@ phase_parse_pay (struct PayContext *pc)
}
}
}
+
+ {
+ unsigned int tokens_ev_index;
+ json_t *token_ev;
+
+ json_array_foreach (tokens_evs, tokens_ev_index, token_ev)
+ {
+ struct TokenEnvelope *ev = &pc->token_envelopes[tokens_ev_index];
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_token_envelope ("token_ev",
+ &ev->blinded_token),
+ GNUNET_JSON_spec_fixed_auto ("h_issue",
+ &ev->h_issue.hash)};
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ token_ev,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ for (unsigned int j = 0; j<tokens_ev_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (ev->blinded_token.blinded_pub,
+ pc->token_envelopes[j].blinded_token.blinded_pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate token envelope in list"));
+ return;
+ }
+ }
+ }
+ }
pc->phase = PP_PARSE_WALLET_DATA;
}