From cddfaf007f4ac22e224f3df5f0151a0d620fb131 Mon Sep 17 00:00:00 2001 From: Özgür Kesim Date: Mon, 27 Jun 2022 17:38:11 +0200 Subject: age commitment: json parser helper and support for purses added --- src/exchange/taler-exchange-httpd_purses_create.c | 57 +++++++++++- src/exchange/taler-exchange-httpd_purses_deposit.c | 57 +++++++++++- src/include/taler_json_lib.h | 23 +++++ src/json/json_helper.c | 103 +++++++++++++++++++++ src/json/json_pack.c | 34 +++++++ src/lib/exchange_api_purse_deposit.c | 44 ++++----- 6 files changed, 289 insertions(+), 29 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c index a981ba582..2c8032342 100644 --- a/src/exchange/taler-exchange-httpd_purses_create.c +++ b/src/exchange/taler-exchange-httpd_purses_create.c @@ -456,6 +456,10 @@ parse_coin (struct MHD_Connection *connection, struct Coin *coin, const json_t *jcoin) { + struct TALER_AgeAttestation attest = {0}; + bool no_attest = true; + struct TALER_AgeCommitment age_commitment = {0}; + bool no_age_commitment = true; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("amount", TEH_currency, @@ -465,10 +469,13 @@ parse_coin (struct MHD_Connection *connection, TALER_JSON_spec_denom_sig ("ub_sig", &coin->cpi.denom_sig), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &coin->cpi.h_age_commitment), - &coin->cpi.no_age_commitment), - // FIXME-Oec: proof of age is missing. + GNUNET_JSON_spec_fixed_auto ("attest", + &attest), + &no_attest), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_age_commitment ("age_commitment", + &age_commitment), + &no_age_commitment), GNUNET_JSON_spec_fixed_auto ("coin_sig", &coin->coin_sig), GNUNET_JSON_spec_fixed_auto ("coin_pub", @@ -591,6 +598,48 @@ parse_coin (struct MHD_Connection *connection, TALER_amount_subtract (&coin->amount_minus_fee, &coin->amount, &coin->deposit_fee)); + + // Check and verify the age restriction. Needs to happen before + // coin-signature check, because we set the h_age_commitment here. + { + if (no_attest != no_age_commitment) + + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + /* FIXME: other error code? */ + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + "mismatch of attest and age_commitment"); + } + + if (! no_age_commitment) + { + // attestation must be valid. + if (GNUNET_OK != + TALER_age_commitment_verify ( + &age_commitment, + pcc->min_age, + &attest)) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + /* FIXME: other error code? */ + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + "invalid attest for minimum age"); + } + + // Save the hash of the age commitment in the coin's public info, so we + // can verify the signature later. + TALER_age_commitment_hash (&age_commitment, + &coin->cpi.h_age_commitment); + coin->cpi.no_age_commitment = false; + + } + } + + /* check coin signature */ switch (dk->denom_pub.cipher) { diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c index 50ed582ad..d65610dc6 100644 --- a/src/exchange/taler-exchange-httpd_purses_deposit.c +++ b/src/exchange/taler-exchange-httpd_purses_deposit.c @@ -300,6 +300,10 @@ parse_coin (struct MHD_Connection *connection, struct Coin *coin, const json_t *jcoin) { + struct TALER_AgeAttestation attest = {0}; + bool no_attest = true; + struct TALER_AgeCommitment age_commitment = {0}; + bool no_age_commitment = true; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("amount", TEH_currency, @@ -309,10 +313,13 @@ parse_coin (struct MHD_Connection *connection, TALER_JSON_spec_denom_sig ("ub_sig", &coin->cpi.denom_sig), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &coin->cpi.h_age_commitment), - &coin->cpi.no_age_commitment), - // FIXME-Oec: proof of age is missing! + GNUNET_JSON_spec_fixed_auto ("attest", + &attest), + &no_attest), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_age_commitment ("age_commitment", + &age_commitment), + &no_age_commitment), GNUNET_JSON_spec_fixed_auto ("coin_sig", &coin->coin_sig), GNUNET_JSON_spec_fixed_auto ("coin_pub", @@ -433,6 +440,47 @@ parse_coin (struct MHD_Connection *connection, TALER_amount_subtract (&coin->amount_minus_fee, &coin->amount, &coin->deposit_fee)); + + // Check and verify the age restriction. Needs to happen before + // coin-signature check, because we set the h_age_commitment here. + { + if (no_attest != no_age_commitment) + + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + /* FIXME: other error code? */ + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + "mismatch of attest and age_commitment"); + } + + if (! no_age_commitment) + { + // attestation must be valid. + if (GNUNET_OK != + TALER_age_commitment_verify ( + &age_commitment, + pcc->min_age, + &attest)) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + /* FIXME: other error code? */ + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + "invalid attest for minimum age"); + } + + // Save the hash of the age commitment in the coin's public info, so we + // can verify the signature later. + TALER_age_commitment_hash (&age_commitment, + &coin->cpi.h_age_commitment); + coin->cpi.no_age_commitment = false; + + } + } + /* check coin signature */ switch (dk->denom_pub.cipher) { @@ -470,6 +518,7 @@ parse_coin (struct MHD_Connection *connection, "total deposit contribution"); } } + { MHD_RESULT mhd_ret = MHD_NO; enum GNUNET_DB_QueryStatus qs; diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 1300f8751..d8912e52e 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -215,6 +215,18 @@ TALER_JSON_pack_econtract ( const char *name, const struct TALER_EncryptedContract *econtract); +/** + * Generate packer instruction for a JSON field of type age_commitment + * + * @param name name of the field to add to the object + * @param age_commitment age commitment to add + * @return json pack specification + */ +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_age_commitment ( + const char *name, + const struct TALER_AgeCommitment *age_commitment); + /** * Convert a TALER amount to a JSON object. @@ -294,6 +306,17 @@ TALER_JSON_spec_econtract (const char *name, struct TALER_EncryptedContract *econtract); +/** + * Provide specification to parse a given JSON object to an age commitment. + * + * @param name name of the age commitment field in the JSON + * @param[out] age_commitment where to store the age commitment + * @return spec for parsing an age commitment + */ +struct GNUNET_JSON_Specification +TALER_JSON_spec_age_commitment (const char *name, + struct TALER_AgeCommitment *age_commitment); + /** * Provide specification to parse given JSON object to an amount * in any currency in network byte order. diff --git a/src/json/json_helper.c b/src/json/json_helper.c index 202caf6f1..cf169384e 100644 --- a/src/json/json_helper.c +++ b/src/json/json_helper.c @@ -401,6 +401,109 @@ TALER_JSON_spec_econtract (const char *name, } +/** + * Parse given JSON object to an age commitmnet + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_age_commitment (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_AgeCommitment *age_commitment = spec->ptr; + json_t *pk; + unsigned int idx; + size_t num; + + if (NULL == root || ! json_is_array (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + num = json_array_size (root); + if (32 <= num || 0 == num) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + age_commitment->num = num; + age_commitment->keys = + GNUNET_new_array (num, + struct TALER_AgeCommitmentPublicKeyP); + + json_array_foreach (root, idx, pk) { + const char *emsg; + unsigned int eline; + struct GNUNET_JSON_Specification pkspec[] = { + GNUNET_JSON_spec_fixed_auto ( + NULL, + &age_commitment->keys[idx].pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pk, + pkspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + }; + + return GNUNET_OK; +} + + +/** + * Cleanup data left fom parsing age commitment + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_age_commitment (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_AgeCommitment *age_commitment = spec->ptr; + + (void) cls; + + if (NULL == age_commitment || + NULL == age_commitment->keys) + return; + + age_commitment->num = 0; + GNUNET_free (age_commitment->keys); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_age_commitment (const char *name, + struct TALER_AgeCommitment *age_commitment) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_age_commitment, + .cleaner = &clean_age_commitment, + .cls = NULL, + .field = name, + .ptr = age_commitment, + .ptr_size = 0, + .size_ptr = NULL + }; + + return ret; +} + + /** * Parse given JSON object to denomination public key. * diff --git a/src/json/json_pack.c b/src/json/json_pack.c index bb52eeb05..ec036f691 100644 --- a/src/json/json_pack.c +++ b/src/json/json_pack.c @@ -71,6 +71,40 @@ TALER_JSON_pack_econtract ( } +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_age_commitment ( + const char *name, + const struct TALER_AgeCommitment *age_commitment) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + json_t *keys; + + if (NULL == age_commitment || + 0 == age_commitment->num) + return ps; + + GNUNET_assert (NULL != + (keys = json_array ())); + + for (size_t i = 0; + i < age_commitment->num; + i++) + { + json_t *val; + val = GNUNET_JSON_from_data (&age_commitment->keys[i], + sizeof(age_commitment->keys[i])); + GNUNET_assert (NULL != val); + GNUNET_assert (0 == + json_array_append_new (keys, val)); + } + + ps.object = keys; + return ps; +} + + struct GNUNET_JSON_PackSpec TALER_JSON_pack_denom_pub ( const char *name, diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c index 6946419dc..836183bbe 100644 --- a/src/lib/exchange_api_purse_deposit.c +++ b/src/lib/exchange_api_purse_deposit.c @@ -485,29 +485,33 @@ TALER_EXCHANGE_purse_deposit ( for (unsigned int i = 0; iage_commitment_proof; struct Coin *coin = &pch->coins[i]; json_t *jdeposit; -#if FIXME_OEC - struct TALER_AgeCommitmentHash agh; - struct TALER_AgeCommitmentHash *aghp = NULL; + struct TALER_AgeCommitmentHash ach; + struct TALER_AgeCommitmentHash *achp = NULL; struct TALER_AgeAttestation attest; + struct TALER_AgeAttestation *attestp = NULL; - TALER_age_commitment_hash (&deposit->age_commitment, - &agh); - aghp = &agh; - if (GNUNET_OK != - TALER_age_commitment_attest (&deposit->age_proof, - min_age, - &attest)) + if (NULL != acp) { - GNUNET_break (0); - json_decref (deposit_arr); - GNUNET_free (pch->base_url); - GNUNET_free (pch->coins); - GNUNET_free (pch); - return NULL; + TALER_age_commitment_hash (&acp->commitment, + &ach); + achp = &ach; + if (GNUNET_OK != + TALER_age_commitment_attest (acp, + min_age, + &attest)) + { + GNUNET_break (0); + json_decref (deposit_arr); + GNUNET_free (pch->base_url); + GNUNET_free (pch->coins); + GNUNET_free (pch); + return NULL; + } + attestp = &attest; } -#endif GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, &coin->coin_pub.eddsa_pub); coin->h_denom_pub = deposit->h_denom_pub; @@ -519,14 +523,12 @@ TALER_EXCHANGE_purse_deposit ( &deposit->coin_priv, &coin->coin_sig); jdeposit = GNUNET_JSON_PACK ( -#if FIXME_OEC GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_data_auto ("h_age_commitment", - aghp)), + achp)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_data_auto ("age_attestation", - &attest)), -#endif + attestp)), TALER_JSON_pack_amount ("amount", &deposit->amount), GNUNET_JSON_pack_data_auto ("denom_pub_hash", -- cgit v1.2.3