diff options
author | Christian Grothoff <christian@grothoff.org> | 2015-06-22 21:18:44 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2015-06-22 21:18:44 +0200 |
commit | 59b4e1cdd81524a2b6b20d34dcdac69b7f1a385e (patch) | |
tree | a76f8e0a6e6c06f6a365c4fbf14e4b7fb4d9c3fd | |
parent | 9fbd7967b15d85f8b524caf84f2e877cccbe949c (diff) |
almost finished checking if reserve transaction history well-formedness for /withdraw/sign error checking
-rw-r--r-- | src/mint-lib/mint_api_json.c | 36 | ||||
-rw-r--r-- | src/mint-lib/mint_api_json.h | 20 | ||||
-rw-r--r-- | src/mint-lib/mint_api_withdraw.c | 242 |
3 files changed, 285 insertions, 13 deletions
diff --git a/src/mint-lib/mint_api_json.c b/src/mint-lib/mint_api_json.c index e2a73bdd4..a28293cfe 100644 --- a/src/mint-lib/mint_api_json.c +++ b/src/mint-lib/mint_api_json.c @@ -72,6 +72,20 @@ parse_json (json_t *root, } break; + case MAJ_CMD_STRING: + { + const char *str; + + str = json_string_value (pos); + if (NULL == str) + { + GNUNET_break_op (0); + return i; + } + *spec[i].details.strptr = str; + } + break; + case MAJ_CMD_BINARY_FIXED: { const char *str; @@ -274,6 +288,8 @@ parse_free (struct MAJ_Specification *spec, break; case MAJ_CMD_BINARY_FIXED: break; + case MAJ_CMD_STRING: + break; case MAJ_CMD_BINARY_VARIABLE: GNUNET_free (*spec[i].details.variable_data.dest_p); *spec[i].details.variable_data.dest_p = NULL; @@ -341,6 +357,26 @@ MAJ_parse_free (struct MAJ_Specification *spec) /** + * The expected field stores a string. + * + * @param name name of the JSON field + * @param strptr where to store a pointer to the field + */ +struct MAJ_Specification +MAJ_spec_string (const char *name, + const char **strptr) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_STRING, + .field = name, + .details.strptr = strptr + }; + return ret; +} + + +/** * Specification for parsing an absolute time value. * * @param name name of the JSON field diff --git a/src/mint-lib/mint_api_json.h b/src/mint-lib/mint_api_json.h index ec3b63cbc..690192510 100644 --- a/src/mint-lib/mint_api_json.h +++ b/src/mint-lib/mint_api_json.h @@ -74,9 +74,9 @@ enum MAJ_Command MAJ_CMD_EDDSA_SIGNATURE, /** - * Parse at current position. + * Parse `const char *` JSON string at current position. */ - MAJ_CMD_B, + MAJ_CMD_STRING, /** * Parse at current position. @@ -176,6 +176,11 @@ struct MAJ_Specification } eddsa_signature; + /** + * Where to store a pointer to the string. + */ + const char **strptr; + } details; }; @@ -230,6 +235,17 @@ MAJ_parse_free (struct MAJ_Specification *spec); /** + * The expected field stores a string. + * + * @param name name of the JSON field + * @param strptr where to store a pointer to the field + */ +struct MAJ_Specification +MAJ_spec_string (const char *name, + const char **strptr); + + +/** * Absolute time. * * @param name name of the JSON field diff --git a/src/mint-lib/mint_api_withdraw.c b/src/mint-lib/mint_api_withdraw.c index c4b81b4be..c5f7b33a2 100644 --- a/src/mint-lib/mint_api_withdraw.c +++ b/src/mint-lib/mint_api_withdraw.c @@ -360,6 +360,11 @@ struct TALER_MINT_WithdrawSignHandle struct GNUNET_HashCode c_hash; /** + * Public key of the reserve we are withdrawing from. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** * The size of the download buffer */ size_t buf_size; @@ -433,6 +438,213 @@ withdraw_sign_ok (struct TALER_MINT_WithdrawSignHandle *wsh, /** + * We got a 402 PAYMENT REQUIRED response for the /withdraw/sign operation. + * Check the signatures on the withdraw transactions in the provided + * history and that the balances add up. We don't do anything directly + * with the information, as the JSON will be returned to the application. + * However, our job is ensuring that the mint followed the protocol, and + * this in particular means checking all of the signatures in the history. + * + * @param wsh operation handle + * @param json reply from the mint + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +withdraw_sign_payment_required (struct TALER_MINT_WithdrawSignHandle *wsh, + json_t *json) +{ + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct TALER_Amount total_in; + struct TALER_Amount total_out; + struct TALER_Amount requested_amount; + json_t *history; + size_t len; + size_t off; + struct MAJ_Specification spec[] = { + MAJ_spec_amount ("balance", &balance), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + history = json_object_get (json, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* FIXME: re-use/share this code with history processing + on /withdraw/status above! */ + /* go over transaction history and compute + total incoming and outgoing amounts */ + len = json_array_size (history); + TALER_amount_get_zero (balance.currency, + &total_in); + TALER_amount_get_zero (balance.currency, + &total_out); + for (off=0;off<len;off++) + { + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct MAJ_Specification hist_spec[] = { + MAJ_spec_string ("type", &type), + MAJ_spec_amount ("amount", + &amount), + /* 'wire' and 'signature' are optional depending on 'type'! */ + MAJ_spec_end + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + MAJ_parse_json (transaction, + hist_spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (0 == strcasecmp (type, + "DEPOSIT")) + { + json_t *wire; + + if (GNUNET_OK != + TALER_amount_add (&total_in, + &total_in, + &amount)) + { + /* overflow in history already!? inconceivable! Bad mint! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + wire = json_object_get (transaction, + "wire"); + /* check 'wire' is a JSON object (no need to check wireformat, + but we do at least expect "some" JSON object here) */ + if ( (NULL == wire) || + (! json_is_object (wire)) ) + { + /* not even a JSON 'wire' specification, not acceptable */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* end type==DEPOSIT */ + } + else if (0 == strcasecmp (type, + "WITHDRAW")) + { + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; + const struct TALER_WithdrawRequestPS *withdraw_purpose; + struct TALER_Amount amount_from_purpose; + struct MAJ_Specification withdraw_spec[] = { + MAJ_spec_eddsa_signed_purpose ("signature", + &purpose, + &wsh->reserve_pub.eddsa_pub), + MAJ_spec_end + }; + + if (GNUNET_OK != + MAJ_parse_json (transaction, + withdraw_spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* Check that the signature actually signed a withdraw request */ + if ( (ntohl (purpose->purpose) != TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW) || + (ntohl (purpose->size) != sizeof (struct TALER_WithdrawRequestPS)) ) + { + GNUNET_break_op (0); + MAJ_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + withdraw_purpose = (const struct TALER_WithdrawRequestPS *) purpose; + TALER_amount_ntoh (&amount_from_purpose, + &withdraw_purpose->amount_with_fee); + if (0 != TALER_amount_cmp (&amount, + &amount_from_purpose)) + { + GNUNET_break_op (0); + MAJ_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + + /* FIXME: ought to also check that the same withdraw transaction + isn't listed twice by the mint... */ + if (GNUNET_OK != + TALER_amount_add (&total_out, + &total_out, + &amount)) + { + /* overflow in history already!? inconceivable! Bad mint! */ + GNUNET_break_op (0); + MAJ_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + /* end type==WITHDRAW */ + } + else + { + /* unexpected 'type', protocol incompatibility, complain! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + + /* check balance = total_in - total_out < withdraw-amount */ + if (GNUNET_SYSERR == + TALER_amount_subtract (&balance_from_history, + &total_in, + &total_out)) + { + /* total_in < total_out, why did the mint ever allow this!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) + { + /* mint cannot add up balances!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* Compute how much we expected to charge to the reserve */ + if (GNUNET_OK != + TALER_amount_add (&requested_amount, + &wsh->pk->value, + &wsh->pk->fee_withdraw)) + { + /* Overflow here? Very strange, our CPU must be fried... */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* Check that funds were really insufficient */ + if (0 < /* >= ??? -- FIXME: check operator! */ + TALER_amount_cmp (&requested_amount, + &balance)) + { + /* mint cannot add up balances!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + + return GNUNET_OK; +} + + +/** * Function called when we're done processing the * HTTP /withdraw/sign request. * @@ -487,19 +699,28 @@ handle_withdraw_sign_finished (void *cls, /* This should never happen, either us or the mint is buggy (or API version conflict); just pass JSON reply to the application */ break; - case MHD_HTTP_FORBIDDEN: - GNUNET_break (0); // FIXME: not implemented + case MHD_HTTP_PAYMENT_REQUIRED: + /* The mint says that the reserve has insufficient funds; + check the signatures in the history... */ + if (GNUNET_OK != + withdraw_sign_payment_required (wsh, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } break; case MHD_HTTP_UNAUTHORIZED: - GNUNET_break (0); // FIXME: not implemented + GNUNET_break (0); /* Nothing really to verify, mint says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_NOT_FOUND: - GNUNET_break (0); // FIXME: not implemented - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ + /* Nothing really to verify, the mint basically just says + that it doesn't know this reserve. Can happen if we + query before the wire transfer went through. + We should simply pass the JSON reply to the application. */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API @@ -594,7 +815,6 @@ TALER_MINT_withdraw_sign (struct TALER_MINT_Handle *mint, { struct TALER_MINT_WithdrawSignHandle *wsh; struct TALER_WithdrawRequestPS req; - struct TALER_ReservePublicKeyP reserve_pub; struct TALER_ReserveSignatureP reserve_sig; struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_MINT_Context *ctx; @@ -619,10 +839,10 @@ TALER_MINT_withdraw_sign (struct TALER_MINT_Handle *mint, pk->key.rsa_public_key, &coin_ev); GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &reserve_pub.eddsa_pub); + &wsh->reserve_pub.eddsa_pub); req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - req.reserve_pub = reserve_pub; + req.reserve_pub = wsh->reserve_pub; if (GNUNET_OK != TALER_amount_add (&amount_with_fee, &pk->fee_withdraw, @@ -652,8 +872,8 @@ TALER_MINT_withdraw_sign (struct TALER_MINT_Handle *mint, "denom_pub", TALER_json_from_rsa_public_key (pk->key.rsa_public_key), "coin_ev", TALER_json_from_data (coin_ev, coin_ev_size), - "reserve_pub", TALER_json_from_data (&reserve_pub, - sizeof (reserve_pub)), + "reserve_pub", TALER_json_from_data (&wsh->reserve_pub, + sizeof (struct TALER_ReservePublicKeyP)), "reserve_sig", TALER_json_from_data (&reserve_sig, sizeof (reserve_sig))); GNUNET_free (coin_ev); |