From c154e50148794f498259b835d9ac064660e769aa Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 30 Jun 2015 21:25:16 +0200 Subject: [PATCH 1/5] fix /test/rsa API to persist private RSA key and use it across requests and allow client to fetch it before requesting the signature --- src/mint/taler-mint-httpd.c | 13 +++- src/mint/taler-mint-httpd_test.c | 102 +++++++++++++++++++++---------- src/mint/taler-mint-httpd_test.h | 44 +++++++++---- 3 files changed, 112 insertions(+), 47 deletions(-) diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c index 51cb14c88..66d7e01e2 100644 --- a/src/mint/taler-mint-httpd.c +++ b/src/mint/taler-mint-httpd.c @@ -242,10 +242,17 @@ handle_mhd_request (void *cls, "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - { "/test/rsa", MHD_HTTP_METHOD_POST, "application/json", + { "/test/rsa/get", MHD_HTTP_METHOD_GET, "application/json", NULL, 0, - &TMH_TEST_handler_test_rsa, MHD_HTTP_OK }, - { "/test/rsa", NULL, "text/plain", + &TMH_TEST_handler_test_rsa_get, MHD_HTTP_OK }, + { "/test/rsa/get", NULL, "text/plain", + "Only GET is allowed", 0, + &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + + { "/test/rsa/sign", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TMH_TEST_handler_test_rsa_sign, MHD_HTTP_OK }, + { "/test/rsa/sign", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, diff --git a/src/mint/taler-mint-httpd_test.c b/src/mint/taler-mint-httpd_test.c index 22c13908e..0c6838ee5 100644 --- a/src/mint/taler-mint-httpd_test.c +++ b/src/mint/taler-mint-httpd_test.c @@ -29,10 +29,16 @@ #include "taler-mint-httpd_responses.h" +/** + * Private key the test module uses for signing. + */ +static struct GNUNET_CRYPTO_rsa_PrivateKey *rsa_pk; + + /** * Handle a "/test/base32" request. Parses the JSON in the post, runs * the Crockford Base32 decoder on the "input" field in the JSON, - * hashes the result and sends the hashed value back as a JSON + * hashes the result and sends the hashed value back as a JSON * string with in Base32 Crockford encoding. Thus, this API * allows testing the hashing and Crockford encoding/decoding * functions. @@ -197,7 +203,7 @@ TMH_TEST_handler_test_hkdf (struct TMH_RequestHandler *rh, TMH_PARSE_MEMBER_VARIABLE ("input"), TMH_PARSE_MEMBER_END }; - + res = TMH_PARSE_post_json (connection, connection_cls, upload_data, @@ -296,11 +302,11 @@ TMH_TEST_handler_test_ecdhe (struct TMH_RequestHandler *rh, /** - * Handle a "/test/eddsa" request. Parses the JSON in the post, + * Handle a "/test/eddsa" request. Parses the JSON in the post, * which must contain a "eddsa_pub" with a public key and an *"eddsa_sig" with the corresponding signature for a purpose * of #TALER_SIGNATURE_CLIENT_TEST_EDDSA. If the signature is - * valid, a reply with a #TALER_SIGNATURE_MINT_TEST_EDDSA is + * valid, a reply with a #TALER_SIGNATURE_MINT_TEST_EDDSA is * returned using the same JSON format. * * @param rh context of the handler @@ -384,9 +390,8 @@ TMH_TEST_handler_test_eddsa (struct TMH_RequestHandler *rh, /** - * Handle a "/test/rsa" request. Parses the JSON in the post, which - * must contain an "blind_ev" blinded value. An RSA public key - * ("rsa_pub") and a blinded signature ("rsa_blind_sig") are returned. + * Handle a "/test/rsa/get" request. Returns the RSA public key + * ("rsa_pub") which is used for signing in "/test/rsa/sign". * * @param rh context of the handler * @param connection the MHD connection to handle @@ -396,21 +401,66 @@ TMH_TEST_handler_test_eddsa (struct TMH_RequestHandler *rh, * @return MHD result code */ int -TMH_TEST_handler_test_rsa (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) +TMH_TEST_handler_test_rsa_get (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + int res; + struct GNUNET_CRYPTO_rsa_PublicKey *pub; + + if (NULL == rsa_pk) + rsa_pk = GNUNET_CRYPTO_rsa_private_key_create (1024); + if (NULL == rsa_pk) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "Failed to create RSA key"); + } + pub = GNUNET_CRYPTO_rsa_private_key_get_public (rsa_pk); + if (NULL == pub) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_error (connection, + "Failed to get public RSA key"); + } + res = TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o}", + "rsa_pub", + TALER_json_from_rsa_public_key (pub)); + GNUNET_CRYPTO_rsa_public_key_free (pub); + return res; +} + + +/** + * Handle a "/test/rsa/sign" request. Parses the JSON in the post, which + * must contain an "blind_ev" blinded value. A a blinded signature + * ("rsa_blind_sig") is returned. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_TEST_handler_test_rsa_sign (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) { json_t *json; int res; - struct GNUNET_CRYPTO_rsa_PublicKey *pub; struct GNUNET_CRYPTO_rsa_Signature *sig; struct TMH_PARSE_FieldSpecification spec[] = { TMH_PARSE_MEMBER_VARIABLE ("blind_ev"), TMH_PARSE_MEMBER_END }; - struct GNUNET_CRYPTO_rsa_PrivateKey *pk; res = TMH_PARSE_post_json (connection, connection_cls, @@ -427,50 +477,38 @@ TMH_TEST_handler_test_rsa (struct TMH_RequestHandler *rh, json_decref (json); if (GNUNET_YES != res) return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - pk = GNUNET_CRYPTO_rsa_private_key_create (1024); - if (NULL == pk) + if (NULL == rsa_pk) + rsa_pk = GNUNET_CRYPTO_rsa_private_key_create (1024); + if (NULL == rsa_pk) { GNUNET_break (0); TMH_PARSE_release_data (spec); return TMH_RESPONSE_reply_internal_error (connection, "Failed to create RSA key"); } - sig = GNUNET_CRYPTO_rsa_sign (pk, + sig = GNUNET_CRYPTO_rsa_sign (rsa_pk, spec[0].destination, spec[0].destination_size_out); if (NULL == sig) { GNUNET_break (0); - GNUNET_CRYPTO_rsa_private_key_free (pk); TMH_PARSE_release_data (spec); return TMH_RESPONSE_reply_internal_error (connection, "Failed to RSA-sign"); } TMH_PARSE_release_data (spec); - pub = GNUNET_CRYPTO_rsa_private_key_get_public (pk); - GNUNET_CRYPTO_rsa_private_key_free (pk); - if (NULL == pub) - { - GNUNET_break (0); - GNUNET_CRYPTO_rsa_signature_free (sig); - return TMH_RESPONSE_reply_internal_error (connection, - "Failed to get public RSA key"); - } res = TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_OK, - "{s:o, s:o}", - "rsa_pub", - TALER_json_from_rsa_public_key (pub), + "{s:o}", "rsa_blind_sig", TALER_json_from_rsa_signature (sig)); GNUNET_CRYPTO_rsa_signature_free (sig); - GNUNET_CRYPTO_rsa_public_key_free (pub); return res; } /** - * Handle a "/test/transfer" request. Parses the JSON in the post, + * Handle a "/test/transfer" request. Parses the JSON in the post, * which must contain a "secret_enc" with the encrypted link secret, * a "trans_priv" with the transfer private key, a "coin_pub" with * a coin public key. A reply with the decrypted "secret" is diff --git a/src/mint/taler-mint-httpd_test.h b/src/mint/taler-mint-httpd_test.h index 1bc5fb66c..33844aab1 100644 --- a/src/mint/taler-mint-httpd_test.h +++ b/src/mint/taler-mint-httpd_test.h @@ -29,7 +29,7 @@ /** * Handle a "/test/base32" request. Parses the JSON in the post, runs * the Crockford Base32 decoder on the "input" field in the JSON, - * hashes the result and sends the hashed value back as a JSON + * hashes the result and sends the hashed value back as a JSON * string with in Base32 Crockford encoding. Thus, this API * allows testing the hashing and Crockford encoding/decoding * functions. @@ -122,11 +122,11 @@ TMH_TEST_handler_test_ecdhe (struct TMH_RequestHandler *rh, /** - * Handle a "/test/eddsa" request. Parses the JSON in the post, + * Handle a "/test/eddsa" request. Parses the JSON in the post, * which must contain a "eddsa_pub" with a public key and an *"ecdsa_sig" with the corresponding signature for a purpose * of #TALER_SIGNATURE_CLIENT_TEST_EDDSA. If the signature is - * valid, a reply with a #TALER_SIGNATURE_MINT_TEST_EDDSA is + * valid, a reply with a #TALER_SIGNATURE_MINT_TEST_EDDSA is * returned using the same JSON format. * * @param rh context of the handler @@ -143,10 +143,10 @@ TMH_TEST_handler_test_eddsa (struct TMH_RequestHandler *rh, const char *upload_data, size_t *upload_data_size); + /** - * Handle a "/test/rsa" request. Parses the JSON in the post, which - * must contain an "blind_ev" blinded value. An RSA public key - * ("rsa_pub") and a blinded signature ("rsa_blind_sig") are returned. + * Handle a "/test/rsa/get" request. Returns the RSA public key + * ("rsa_pub") which is used for signing in "/test/rsa/sign". * * @param rh context of the handler * @param connection the MHD connection to handle @@ -156,15 +156,35 @@ TMH_TEST_handler_test_eddsa (struct TMH_RequestHandler *rh, * @return MHD result code */ int -TMH_TEST_handler_test_rsa (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); +TMH_TEST_handler_test_rsa_get (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); /** - * Handle a "/test/transfer" request. Parses the JSON in the post, + * Handle a "/test/rsa/sign" request. Parses the JSON in the post, which + * must contain an "blind_ev" blinded value. A a blinded signature + * ("rsa_blind_sig") is returned. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_TEST_handler_test_rsa_sign (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/test/transfer" request. Parses the JSON in the post, * which must contain a "secret_enc" with the encrypted link secret, * a "trans_priv" with the transfer private key, a "coin_pub" with * a coin public key. A reply with the decrypted "secret" is From 68bf92de2c1d754a04663236a4106e2c5635ebc4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 30 Jun 2015 21:26:16 +0200 Subject: [PATCH 2/5] fix ftbfs --- src/mint-lib/mint_api_handle.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mint-lib/mint_api_handle.c b/src/mint-lib/mint_api_handle.c index 6bcae9f60..918a0abf4 100644 --- a/src/mint-lib/mint_api_handle.c +++ b/src/mint-lib/mint_api_handle.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "taler_mint_service.h" #include "taler_signatures.h" #include "mint_api_context.h" @@ -588,7 +589,7 @@ keys_completed_cb (void *cls, } switch (response_code) { case 0: - kr->errno = 1; + kr->eno = 1; break; case MHD_HTTP_OK: break; @@ -596,7 +597,7 @@ keys_completed_cb (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Mint returned status code %u for /keys\n", response_code); - kr->errno = 1; + kr->eno = 1; break; } From 253d220ea54d45557f33dd3a7affef0e79593218 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 30 Jun 2015 22:09:15 +0200 Subject: [PATCH 3/5] towards implementing #3851: /admin/add/incoming --- src/mint/Makefile.am | 1 + src/mint/taler-mint-httpd.c | 10 ++++++++++ src/mint/taler-mint-httpd_db.c | 24 ++++++++++++++++++++++++ src/mint/taler-mint-httpd_db.h | 19 +++++++++++++++++++ src/mint/taler-mint-httpd_parsing.h | 2 ++ src/mint/taler-mint-httpd_responses.c | 19 +++++++++++++++++++ src/mint/taler-mint-httpd_responses.h | 12 ++++++++++++ 7 files changed, 87 insertions(+) diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am index 2cda4fac1..112dbbf18 100644 --- a/src/mint/Makefile.am +++ b/src/mint/Makefile.am @@ -11,6 +11,7 @@ taler_mint_httpd_SOURCES = \ taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ + taler-mint-httpd_admin.c taler-mint-httpd_admin.h \ taler-mint-httpd_deposit.c taler-mint-httpd_deposit.h \ taler-mint-httpd_withdraw.c taler-mint-httpd_withdraw.h \ taler-mint-httpd_refresh.c taler-mint-httpd_refresh.h diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c index 66d7e01e2..a0f1eca4b 100644 --- a/src/mint/taler-mint-httpd.c +++ b/src/mint/taler-mint-httpd.c @@ -28,6 +28,7 @@ #include #include "taler-mint-httpd_parsing.h" #include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_admin.h" #include "taler-mint-httpd_deposit.h" #include "taler-mint-httpd_withdraw.h" #include "taler-mint-httpd_refresh.h" @@ -199,6 +200,15 @@ handle_mhd_request (void *cls, "Only GET is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + /* FIXME: maybe conditionally compile these? */ + { "/admin/add/incoming", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TMH_ADMIN_handler_admin_add_incoming, MHD_HTTP_OK }, + { "/admin/add/incoming", NULL, "text/plain", + "Only POST is allowed", 0, + &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + + #if HAVE_DEVELOPER { "/test", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 78a8d6f40..512ed9a48 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -1374,4 +1374,28 @@ TMH_DB_execute_refresh_link (struct MHD_Connection *connection, } +/** + * Add an incoming transaction to the database. Checks if the + * transaction is fresh (not a duplicate) and if so adds it to + * the database. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve + * @param amount amount to add to the reserve + * @param execution_time when did we receive the wire transfer + * @param wire details about the wire transfer + * @return MHD result code + */ +int +TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute execution_time, + json_t *wire) +{ + GNUNET_break (0); // FIXME: #3851! + return MHD_NO; +} + + /* end of taler-mint-httpd_db.c */ diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h index 56a4dd9cb..8a171153a 100644 --- a/src/mint/taler-mint-httpd_db.h +++ b/src/mint/taler-mint-httpd_db.h @@ -167,5 +167,24 @@ TMH_DB_execute_refresh_link (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub); + +/** + * Add an incoming transaction to the database. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve + * @param amount amount to add to the reserve + * @param execution_time when did we receive the wire transfer + * @param wire details about the wire transfer + * @return MHD result code + */ +int +TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute execution_time, + json_t *wire); + + #endif /* TALER_MINT_HTTPD_DB_H */ diff --git a/src/mint/taler-mint-httpd_parsing.h b/src/mint/taler-mint-httpd_parsing.h index 4714ab6a0..2439f39f9 100644 --- a/src/mint/taler-mint-httpd_parsing.h +++ b/src/mint/taler-mint-httpd_parsing.h @@ -134,6 +134,7 @@ enum TMH_PARSE_JsonNavigationCommand * Param: struct GNUNET_TIME_Absolute * */ TMH_PARSE_JNC_RET_TIME_ABSOLUTE + }; @@ -299,6 +300,7 @@ TMH_PARSE_release_data (struct TMH_PARSE_FieldSpecification *spec); */ #define TMH_PARSE_MEMBER_TIME_ABS(field,atime) { field, atime, sizeof(*atime), 0, TMH_PARSE_JNC_RET_TIME_ABSOLUTE, 0 } + /** * Generate line in parser specification indicating the end of the spec. */ diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index ccc144e29..013cc19b8 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -171,6 +171,25 @@ TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, } +/** + * Send a response indicating permission denied. + * + * @param connection the MHD connection to use + * @param hint hint about why access was denied + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_permission_denied (struct MHD_Connection *connection, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_FORBIDDEN, + "{s:s, s:s}", + "error", "permission denied", + "hint", hint); +} + + /** * Send a response indicating an internal error. * diff --git a/src/mint/taler-mint-httpd_responses.h b/src/mint/taler-mint-httpd_responses.h index 2ae06209d..1f8e1e44c 100644 --- a/src/mint/taler-mint-httpd_responses.h +++ b/src/mint/taler-mint-httpd_responses.h @@ -114,6 +114,18 @@ TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, const char *param_name); +/** + * Send a response indicating permission denied. + * + * @param connection the MHD connection to use + * @param hint hint about why access was denied + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_permission_denied (struct MHD_Connection *connection, + const char *hint); + + /** * Send a response indicating an internal error. * From f948a10f712ebd6f52182dc4bf71deee1c45b35f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 1 Jul 2015 00:01:21 +0200 Subject: [PATCH 4/5] implementing #3851 --- src/include/taler_mintdb_plugin.h | 2 ++ src/mint-tools/taler-mint-reservemod.c | 2 ++ src/mint/taler-mint-httpd_db.c | 29 ++++++++++++++++++++++++-- src/mintdb/plugin_mintdb_postgres.c | 8 +++---- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/include/taler_mintdb_plugin.h b/src/include/taler_mintdb_plugin.h index 88fe2801e..d7f0f99cd 100644 --- a/src/include/taler_mintdb_plugin.h +++ b/src/include/taler_mintdb_plugin.h @@ -742,6 +742,7 @@ struct TALER_MINTDB_Plugin * @param db the database connection handle * @param reserve_pub public key of the reserve * @param balance the amount that has to be added to the reserve + * @param execution_time when was the amount added * @param details bank transaction details justifying the increment, * must be unique for each incoming transaction * @return #GNUNET_OK upon success; #GNUNET_NO if the given @@ -753,6 +754,7 @@ struct TALER_MINTDB_Plugin struct TALER_MINTDB_Session *db, const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_Amount *balance, + struct GNUNET_TIME_Absolute execution_time, const json_t *details); diff --git a/src/mint-tools/taler-mint-reservemod.c b/src/mint-tools/taler-mint-reservemod.c index 888b34baa..11d67ed5d 100644 --- a/src/mint-tools/taler-mint-reservemod.c +++ b/src/mint-tools/taler-mint-reservemod.c @@ -172,10 +172,12 @@ main (int argc, char *const *argv) error.source); goto cleanup; } + /* FIXME: maybe allow passing timestamp via command-line? */ ret = plugin->reserves_in_insert (plugin->cls, session, &reserve_pub, &add_value, + GNUNET_TIME_absolute_get (), jdetails); json_decref (jdetails); if (GNUNET_SYSERR == ret) diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 512ed9a48..e4e8c59ec 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -1393,8 +1393,33 @@ TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, struct GNUNET_TIME_Absolute execution_time, json_t *wire) { - GNUNET_break (0); // FIXME: #3851! - return MHD_NO; + struct TALER_MINTDB_Session *session; + int ret; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ret = TMH_plugin->reserves_in_insert (TMH_plugin->cls, + session, + reserve_pub, + amount, + execution_time, + wire); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s}", + "status", + (GNUNET_OK == ret) + ? "NEW" + : "DUP"); } diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c index 836a8a8c8..81a372e4a 100644 --- a/src/mintdb/plugin_mintdb_postgres.c +++ b/src/mintdb/plugin_mintdb_postgres.c @@ -1284,6 +1284,7 @@ reserves_update (void *cls, * @param session the database connection handle * @param reserve_pub public key of the reserve * @param balance the amount that has to be added to the reserve + * @param execution_time when was the amount added * @param details bank transaction details justifying the increment, * must be unique for each incoming transaction * @return #GNUNET_OK upon success; #GNUNET_NO if the given @@ -1295,12 +1296,12 @@ postgres_reserves_in_insert (void *cls, struct TALER_MINTDB_Session *session, const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_Amount *balance, + struct GNUNET_TIME_Absolute execution_time, const json_t *details) { PGresult *result; int reserve_exists; struct TALER_MINTDB_Reserve reserve; - struct GNUNET_TIME_Absolute now; struct GNUNET_TIME_Absolute expiry; if (GNUNET_OK != postgres_start (cls, @@ -1318,8 +1319,7 @@ postgres_reserves_in_insert (void *cls, GNUNET_break (0); goto rollback; } - now = GNUNET_TIME_absolute_get (); - expiry = GNUNET_TIME_absolute_add (now, + expiry = GNUNET_TIME_absolute_add (execution_time, TALER_IDLE_RESERVE_EXPIRATION_TIME); if (GNUNET_NO == reserve_exists) { @@ -1358,7 +1358,7 @@ postgres_reserves_in_insert (void *cls, TALER_PQ_query_param_auto_from_type (&reserve.pub), TALER_PQ_query_param_amount (balance), TALER_PQ_query_param_json (details), - TALER_PQ_query_param_absolute_time (&now), + TALER_PQ_query_param_absolute_time (&execution_time), TALER_PQ_query_param_end }; From 01b2a257b1add6809ffa734b2fe6bc9ac87f5477 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 1 Jul 2015 00:18:01 +0200 Subject: [PATCH 5/5] implement C API for /admin/add/incoming --- src/include/taler_mint_service.h | 88 ++++++-- src/mint-lib/Makefile.am | 1 + src/mint-lib/mint_api_admin.c | 334 ++++++++++++++++++++++++++++++ src/mint/taler-mint-httpd_admin.c | 152 ++++++++++++++ src/mint/taler-mint-httpd_admin.h | 46 ++++ 5 files changed, 609 insertions(+), 12 deletions(-) create mode 100644 src/mint-lib/mint_api_admin.c create mode 100644 src/mint/taler-mint-httpd_admin.c create mode 100644 src/mint/taler-mint-httpd_admin.h diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h index 0e79a5f73..6edd1e428 100644 --- a/src/include/taler_mint_service.h +++ b/src/include/taler_mint_service.h @@ -363,12 +363,13 @@ typedef void /** - * Submit a deposit permission to the mint and get the mint's response. - * Note that while we return the response verbatim to the caller for - * further processing, we do already verify that the response is - * well-formed (i.e. that signatures included in the response are all - * valid). If the mint's reply is not well-formed, we return an - * HTTP status code of zero to @a cb. + * Submit a deposit permission to the mint and get the mint's + * response. This API is typically used by a merchant. Note that + * while we return the response verbatim to the caller for further + * processing, we do already verify that the response is well-formed + * (i.e. that signatures included in the response are all valid). If + * the mint's reply is not well-formed, we return an HTTP status code + * of zero to @a cb. * * We also verify that the @a coin_sig is valid for this deposit * request, and that the @a ub_sig is a valid signature for @a @@ -564,11 +565,12 @@ typedef void /** - * Withdraw a coin from the mint using a /withdraw/sign request. Note - * that to ensure that no money is lost in case of hardware failures, - * the caller must have committed (most of) the arguments to disk - * before calling, and be ready to repeat the request with the same - * arguments in case of failures. + * Withdraw a coin from the mint using a /withdraw/sign request. This + * API is typically used by a wallet. Note that to ensure that no + * money is lost in case of hardware failures, the caller must have + * committed (most of) the arguments to disk before calling, and be + * ready to repeat the request with the same arguments in case of + * failures. * * @param mint the mint handle; the mint must be ready to operate * @param pk kind of coin to create @@ -578,7 +580,7 @@ typedef void * caller must have committed this value to disk before the call (with @a pk) * @param res_cb the callback to call when the final result for this request is available * @param res_cb_cls closure for the above callback - * @return #GNUNET_OK on success, #GNUNET_SYSERR + * @return NULL * if the inputs are invalid (i.e. denomination key not with this mint). * In this case, the callback is not called. */ @@ -602,5 +604,67 @@ void TALER_MINT_withdraw_sign_cancel (struct TALER_MINT_WithdrawSignHandle *sign); +/* ********************* /admin/add/incoming *********************** */ + + + +/** + * @brief A /admin/add/incoming Handle + */ +struct TALER_MINT_AdminAddIncomingHandle; + + +/** + * Callbacks of this type are used to serve the result of submitting + * information about an incoming transaction to a mint. + * + * @param cls closure + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param full_response full response from the mint (for logging, in case of errors) + */ +typedef void +(*TALER_MINT_AdminAddIncomingResultCallback) (void *cls, + unsigned int http_status, + json_t *full_response); + + +/** + * Notify the mint that we have received an incoming transaction + * which fills a reserve. Note that this API is an administrative + * API and thus not accessible to typical mint clients, but only + * to the operators of the mint. + * + * @param mint the mint handle; the mint must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + * if the inputs are invalid (i.e. invalid amount). + * In this case, the callback is not called. + */ +struct TALER_MINT_AdminAddIncomingHandle * +TALER_MINT_admin_add_incoming (struct TALER_MINT_Handle *mint, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + const struct GNUNET_TIME_Absolute execution_date, + const json_t *wire, + TALER_MINT_AdminAddIncomingResultCallback res_cb, + void *res_cb_cls); + + +/** + * Cancel an add incoming. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param sign the admin add incoming request handle + */ +void +TALER_MINT_admin_add_incoming_cancel (struct TALER_MINT_AdminAddIncomingHandle *aai); + + #endif /* _TALER_MINT_SERVICE_H */ diff --git a/src/mint-lib/Makefile.am b/src/mint-lib/Makefile.am index f365b3f1f..edd26a025 100644 --- a/src/mint-lib/Makefile.am +++ b/src/mint-lib/Makefile.am @@ -17,6 +17,7 @@ libtalermint_la_SOURCES = \ mint_api_context.c mint_api_context.h \ mint_api_json.c mint_api_json.h \ mint_api_handle.c mint_api_handle.h \ + mint_api_admin.c \ mint_api_deposit.c \ mint_api_withdraw.c diff --git a/src/mint-lib/mint_api_admin.c b/src/mint-lib/mint_api_admin.c new file mode 100644 index 000000000..c058cdcfe --- /dev/null +++ b/src/mint-lib/mint_api_admin.c @@ -0,0 +1,334 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see + +*/ +/** + * @file mint-lib/mint_api_admin.c + * @brief Implementation of the /admin/ requests of the mint's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include "taler_mint_service.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * Print JSON parsing related error information + */ +#define JSON_WARN(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)", \ + __FILE__, __LINE__, error.text, error.source) + + +/** + * @brief An admin/add/incoming Handle + */ +struct TALER_MINT_AdminAddIncomingHandle +{ + + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * HTTP headers for the request. + */ + struct curl_slist *headers; + + /** + * Function to call with the result. + */ + TALER_MINT_AdminAddIncomingResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + void *buf; + + /** + * The size of the download buffer + */ + size_t buf_size; + + /** + * Error code (based on libc errno) if we failed to download + * (i.e. response too large). + */ + int eno; + +}; + + +/** + * Function called when we're done processing the + * HTTP /admin/add/incoming request. + * + * @param cls the `struct TALER_MINT_AdminAddIncomingHandle` + */ +static void +handle_admin_add_incoming_finished (void *cls, + CURL *eh) +{ + struct TALER_MINT_AdminAddIncomingHandle *aai = cls; + long response_code; + json_error_t error; + json_t *json; + + json = NULL; + if (0 == aai->eno) + { + json = json_loadb (aai->buf, + aai->buf_size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, + &error); + if (NULL == json) + { + JSON_WARN (error); + response_code = 0; + } + } + if (NULL != json) + { + if (CURLE_OK != + curl_easy_getinfo (eh, + CURLINFO_RESPONSE_CODE, + &response_code)) + { + /* unexpected error... */ + GNUNET_break (0); + response_code = 0; + } + } + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + break; + case MHD_HTTP_BAD_REQUEST: + /* 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: + /* Access denied */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* 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: + /* Nothing really to verify, this should never + happen, we should 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 + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break (0); + response_code = 0; + break; + } + aai->cb (aai->cb_cls, + response_code, + json); + json_decref (json); + TALER_MINT_admin_add_incoming_cancel (aai); +} + + +/** + * Callback used when downloading the reply to a /admin/add/incoming + * request. Just appends all of the data to the `buf` in the `struct + * TALER_MINT_AdminAddIncomingHandle` for further processing. The size + * of the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if the + * download exceeds this size, we abort with an error. + * + * @param bufptr data downloaded via HTTP + * @param size size of an item in @a bufptr + * @param nitems number of items in @a bufptr + * @param cls the `struct TALER_MINT_DepositHandle` + * @return number of bytes processed from @a bufptr + */ +static int +admin_add_incoming_download_cb (char *bufptr, + size_t size, + size_t nitems, + void *cls) +{ + struct TALER_MINT_AdminAddIncomingHandle *aai = cls; + size_t msize; + void *buf; + + if (0 == size * nitems) + { + /* Nothing (left) to do */ + return 0; + } + msize = size * nitems; + if ( (msize + aai->buf_size) >= GNUNET_MAX_MALLOC_CHECKED) + { + aai->eno = ENOMEM; + return 0; /* signals an error to curl */ + } + aai->buf = GNUNET_realloc (aai->buf, + aai->buf_size + msize); + buf = aai->buf + aai->buf_size; + memcpy (buf, bufptr, msize); + aai->buf_size += msize; + return msize; +} + + +/** + * Notify the mint that we have received an incoming transaction + * which fills a reserve. Note that this API is an administrative + * API and thus not accessible to typical mint clients, but only + * to the operators of the mint. + * + * @param mint the mint handle; the mint must be ready to operate + * @param reserve_pub public key of the reserve + * @param amount amount that was deposited + * @param execution_date when did we receive the amount + * @param wire wire details + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return NULL + * if the inputs are invalid (i.e. invalid amount). + * In this case, the callback is not called. + */ +struct TALER_MINT_AdminAddIncomingHandle * +TALER_MINT_admin_add_incoming (struct TALER_MINT_Handle *mint, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + const struct GNUNET_TIME_Absolute execution_date, + const json_t *wire, + TALER_MINT_AdminAddIncomingResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_MINT_AdminAddIncomingHandle *aai; + struct TALER_MINT_Context *ctx; + json_t *admin_obj; + CURL *eh; + + if (GNUNET_YES != + MAH_handle_is_ready (mint)) + { + GNUNET_break (0); + return NULL; + } + admin_obj = json_pack ("{s:o, s:o," /* reserve_pub/amount */ + " s:o, s:o}", /* execution_Date/wire */ + "reserve_pub", TALER_json_from_data (reserve_pub, + sizeof (*reserve_pub)), + "amount", TALER_json_from_amount (amount), + "execution_date", TALER_json_from_abs (execution_date), + "wire", wire); + aai = GNUNET_new (struct TALER_MINT_AdminAddIncomingHandle); + aai->mint = mint; + aai->cb = res_cb; + aai->cb_cls = res_cb_cls; + aai->url = MAH_path_to_url (mint, "/admin/add/incoming"); + + eh = curl_easy_init (); + GNUNET_assert (NULL != (aai->json_enc = + json_dumps (admin_obj, + JSON_COMPACT))); + json_decref (admin_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + aai->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + aai->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (aai->json_enc))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &admin_add_incoming_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + aai)); + GNUNET_assert (NULL != (aai->headers = + curl_slist_append (aai->headers, + "Content-Type: application/json"))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HTTPHEADER, + aai->headers)); + ctx = MAH_handle_to_context (mint); + aai->job = MAC_job_add (ctx, + eh, + &handle_admin_add_incoming_finished, + aai); + return aai; +} + + +/** + * Cancel an add incoming. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param sign the admin add incoming request handle + */ +void +TALER_MINT_admin_add_incoming_cancel (struct TALER_MINT_AdminAddIncomingHandle *aai) +{ + MAC_job_cancel (aai->job); + curl_slist_free_all (aai->headers); + GNUNET_free (aai->url); + GNUNET_free (aai->json_enc); + GNUNET_free (aai); +} + + +/* end of mint_api_admin.c */ diff --git a/src/mint/taler-mint-httpd_admin.c b/src/mint/taler-mint-httpd_admin.c new file mode 100644 index 000000000..22ead53ee --- /dev/null +++ b/src/mint/taler-mint-httpd_admin.c @@ -0,0 +1,152 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see +*/ +/** + * @file taler-mint-httpd_admin.c + * @brief Handle /admin/ requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler-mint-httpd_admin.h" +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" + + +/** + * Check permissions (we only allow access to /admin/ from loopback). + * + * @param connection connection to perform access check for + * @return #GNUNET_OK if permitted, + * #GNUNET_NO if denied and error was queued, + * #GNUNET_SYSERR if denied and we failed to report + */ +static int +check_permissions (struct MHD_Connection *connection) +{ + const union MHD_ConnectionInfo *ci; + const struct sockaddr *addr; + int res; + + ci = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_CLIENT_ADDRESS); + if (NULL == ci) + { + GNUNET_break (0); + res = TMH_RESPONSE_reply_internal_error (connection, + "Failed to verify client address"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + addr = ci->client_addr; + switch (addr->sa_family) + { + case AF_INET: + { + const struct sockaddr_in *sin = (const struct sockaddr_in *) addr; + + if (INADDR_LOOPBACK != sin->sin_addr.s_addr) + { + res = TMH_RESPONSE_reply_permission_denied (connection, + "/admin/ only allowed via loopback"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) addr; + + if (! IN6_IS_ADDR_LOOPBACK (&sin6->sin6_addr)) + { + res = TMH_RESPONSE_reply_permission_denied (connection, + "/admin/ only allowed via loopback"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + break; + } + default: + GNUNET_break (0); + res = TMH_RESPONSE_reply_internal_error (connection, + "Unsupported AF"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + + +/** + * Handle a "/admin/add/incoming" request. Parses the + * given "reserve_pub", "amount", "transaction" and "h_wire" + * details and adds the respective transaction to the database. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute at; + json_t *wire; + json_t *root; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_MEMBER_FIXED ("reserve_pub", &reserve_pub), + TMH_PARSE_MEMBER_AMOUNT ("amount", &amount), + TMH_PARSE_MEMBER_TIME_ABS ("execution_date", &at), + TMH_PARSE_MEMBER_OBJECT ("wire", &wire), + TMH_PARSE_MEMBER_END + }; + int res; + + res = check_permissions (connection); + if (GNUNET_OK != res) + return (GNUNET_OK == res) ? MHD_YES : MHD_NO; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == root) ) + return MHD_YES; + res = TMH_PARSE_json_data (connection, + root, + spec); + json_decref (root); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + res = TMH_DB_execute_admin_add_incoming (connection, + &reserve_pub, + &amount, + at, + wire); + TMH_PARSE_release_data (spec); + return res; +} + +/* end of taler-mint-httpd_admin.c */ diff --git a/src/mint/taler-mint-httpd_admin.h b/src/mint/taler-mint-httpd_admin.h new file mode 100644 index 000000000..b8ca3ce59 --- /dev/null +++ b/src/mint/taler-mint-httpd_admin.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see +*/ +/** + * @file taler-mint-httpd_admin.h + * @brief Handle /admin/ requests + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_ADMIN_H +#define TALER_MINT_HTTPD_ADMIN_H + +#include +#include "taler-mint-httpd.h" + +/** + * Handle a "/admin/add/incoming" request. Parses the + * given "reserve_pub", "amount", "transaction" and "h_wire" + * details and adds the respective transaction to the database. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif