diff options
author | Christian Grothoff <christian@grothoff.org> | 2016-04-15 15:00:26 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2016-04-15 15:00:26 +0200 |
commit | 74e237164ce7958c19467ad440e45576c4425570 (patch) | |
tree | e5156b7197cbb76ad0b0d617d9a04eeb2c08ea48 /src/wire | |
parent | ebf049a8c2d982e723fe67dbff64e1eea14d8247 (diff) | |
parent | 3098c0a9e0b18a436e484ef693cdebeb16d4c131 (diff) | |
download | exchange-74e237164ce7958c19467ad440e45576c4425570.tar.gz exchange-74e237164ce7958c19467ad440e45576c4425570.tar.bz2 exchange-74e237164ce7958c19467ad440e45576c4425570.zip |
Merge branch 'master' of ssh://taler.net:/var/git/exchange
Diffstat (limited to 'src/wire')
-rw-r--r-- | src/wire/Makefile.am | 31 | ||||
-rw-r--r-- | src/wire/plugin_wire_sepa.c | 292 | ||||
-rw-r--r-- | src/wire/plugin_wire_template.c | 53 | ||||
-rw-r--r-- | src/wire/plugin_wire_test.c | 393 | ||||
-rw-r--r-- | src/wire/test_sepa_wireformat.c | 34 | ||||
-rw-r--r-- | src/wire/test_wire_plugin.c | 196 | ||||
-rw-r--r-- | src/wire/test_wire_plugin.conf | 21 | ||||
-rw-r--r-- | src/wire/test_wire_plugin_key.priv | 1 | ||||
-rw-r--r-- | src/wire/test_wire_plugin_sepa.json | 8 | ||||
-rw-r--r-- | src/wire/test_wire_plugin_test.json | 7 | ||||
-rw-r--r-- | src/wire/wire-sepa.conf | 10 | ||||
-rw-r--r-- | src/wire/wire-test.conf | 16 |
12 files changed, 957 insertions, 105 deletions
diff --git a/src/wire/Makefile.am b/src/wire/Makefile.am index eb2e893fa..debb27828 100644 --- a/src/wire/Makefile.am +++ b/src/wire/Makefile.am @@ -6,6 +6,18 @@ if USE_COVERAGE XLIB = -lgcov endif +pkgcfgdir = $(prefix)/share/taler/config.d/ + +pkgcfg_DATA = \ + wire-sepa.conf \ + wire-test.conf + + +EXTRA_DIST = \ + wire-sepa.conf \ + wire-test.conf \ + test_wire_plugin.conf + plugindir = $(libdir)/taler plugin_LTLIBRARIES = \ @@ -36,7 +48,9 @@ libtaler_plugin_wire_sepa_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_wire_sepa_la_LDFLAGS = \ $(TALER_PLUGIN_LDFLAGS) \ + $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ -lgnunetutil $(XLIB) @@ -60,11 +74,15 @@ libtalerwire_la_LDFLAGS = \ -export-dynamic -no-undefined +AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; + TESTS = \ - test_sepa_wireformat + test_sepa_wireformat \ + test_wire_plugin check_PROGRAMS= \ - test_sepa_wireformat + test_sepa_wireformat \ + test_wire_plugin @@ -76,3 +94,12 @@ test_sepa_wireformat_LDADD = \ libtalerwire.la \ $(top_builddir)/src/util/libtalerutil.la + +test_wire_plugin_SOURCES = \ + test_wire_plugin.c +test_wire_plugin_LDADD = \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + libtalerwire.la \ + $(top_builddir)/src/util/libtalerutil.la diff --git a/src/wire/plugin_wire_sepa.c b/src/wire/plugin_wire_sepa.c index 00d19d4b0..6f01167d9 100644 --- a/src/wire/plugin_wire_sepa.c +++ b/src/wire/plugin_wire_sepa.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016 GNUnet e.V. + Copyright (C) 2016 GNUnet e.V. & Inria 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 @@ -23,6 +23,8 @@ */ #include "platform.h" #include "taler_wire_plugin.h" +#include "taler_signatures.h" +#include <gnunet/gnunet_json_lib.h> /** @@ -58,6 +60,8 @@ sepa_amount_round (void *cls, struct SepaClosure *sc = cls; uint32_t delta; + if (NULL == sc->currency) + return GNUNET_SYSERR; if (0 != strcasecmp (amount->currency, sc->currency)) { @@ -348,41 +352,138 @@ validate_iban (const char *iban) /** + * Compute purpose for signing. + * + * @param sepa_name name of the account holder + * @param iban bank account number in IBAN format + * @param bic bank identifier + * @param[out] mp purpose to be signed + */ +static void +compute_purpose (const char *sepa_name, + const char *iban, + const char *bic, + struct TALER_MasterWireDetailsPS *wsd) +{ + struct GNUNET_HashContext *hc; + + wsd->purpose.size = htonl (sizeof (struct TALER_MasterWireDetailsPS)); + wsd->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SEPA_DETAILS); + hc = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hc, + "sepa", + strlen ("sepa") + 1); + GNUNET_CRYPTO_hash_context_read (hc, + sepa_name, + strlen (sepa_name) + 1); + GNUNET_CRYPTO_hash_context_read (hc, + iban, + strlen (iban) + 1); + GNUNET_CRYPTO_hash_context_read (hc, + bic, + strlen (bic) + 1); + GNUNET_CRYPTO_hash_context_finish (hc, + &wsd->h_sepa_details); +} + + +/** + * Verify that the signature in the @a json for /wire/sepa is valid. + * + * @param json json reply with the signature + * @param master_pub public key of the exchange to verify against + * @return #GNUNET_SYSERR if @a json is invalid, + * #GNUNET_NO if the method is unknown, + * #GNUNET_OK if the json is valid + */ +static int +verify_wire_sepa_signature_ok (const json_t *json, + const struct TALER_MasterPublicKeyP *master_pub) +{ + struct TALER_MasterSignatureP exchange_sig; + struct TALER_MasterWireDetailsPS mp; + const char *name; + const char *iban; + const char *bic; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", &exchange_sig), + GNUNET_JSON_spec_string ("name", &name), + GNUNET_JSON_spec_string ("iban", &iban), + GNUNET_JSON_spec_string ("bic", &bic), + GNUNET_JSON_spec_end() + }; + + if (NULL == master_pub) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Skipping signature check as master public key not given\n"); + return GNUNET_OK; + } + if (GNUNET_OK != + GNUNET_JSON_parse (json, spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + compute_purpose (name, + iban, + bic, + &mp); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SEPA_DETAILS, + &mp.purpose, + &exchange_sig.eddsa_signature, + &master_pub->eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** * Check if the given wire format JSON object is correctly formatted * + * @param cls the @e cls of this struct with the plugin-specific state * @param wire the JSON wire format object + * @param master_pub public key of the exchange to verify against * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not */ static int -sepa_wire_validate (const json_t *wire) +sepa_wire_validate (void *cls, + const json_t *wire, + const struct TALER_MasterPublicKeyP *master_pub) { json_error_t error; const char *type; const char *iban; const char *name; const char *bic; - uint64_t r; - const char *address; if (0 != json_unpack_ex ((json_t *) wire, - &error, JSON_STRICT, + &error, 0, "{" - "s:s," /* TYPE: sepa */ - "s:s," /* IBAN: iban */ + "s:s," /* type: sepa */ + "s:s," /* iban: IBAN */ "s:s," /* name: beneficiary name */ - "s:s," /* BIC: beneficiary bank's BIC */ - "s:i," /* r: random 64-bit integer nounce */ - "s:s" /* address: address of the beneficiary */ + "s:s" /* bic: beneficiary bank's BIC */ "}", "type", &type, - "IBAN", &iban, + "iban", &iban, "name", &name, - "bic", &bic, - "r", &r, - "address", &address)) + "bic", &bic)) { - TALER_json_warn (error); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "JSON parsing failed at %s:%u: %s (%s)\n", + __FILE__, __LINE__, + error.text, error.source); + json_dumpf (wire, stderr, 0); + fprintf (stderr, "\n"); return GNUNET_SYSERR; } if (0 != strcasecmp (type, @@ -400,11 +501,144 @@ sepa_wire_validate (const json_t *wire) iban); return GNUNET_NO; } + /* FIXME: don't parse again, integrate properly... */ + if (GNUNET_OK != + verify_wire_sepa_signature_ok (wire, + master_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signature invalid\n"); + return GNUNET_NO; + } return GNUNET_YES; } /** + * Obtain wire transfer details in the plugin-specific format + * from the configuration. + * + * @param cls closure + * @param cfg configuration with details about wire accounts + * @param account_name which section in the configuration should we parse + * @return NULL if @a cfg fails to have valid wire details for @a account_name + */ +static json_t * +sepa_get_wire_details (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *account_name) +{ + char *sepa_wire_file; + json_error_t err; + json_t *ret; + + /* Fetch reply */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + account_name, + "SEPA_RESPONSE_FILE", + &sepa_wire_file)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + account_name, + "SEPA_RESPONSE_FILE"); + return NULL; + } + ret = json_load_file (sepa_wire_file, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse JSON in %s: %s (%s:%u)\n", + sepa_wire_file, + err.text, + err.source, + err.line); + GNUNET_free (sepa_wire_file); + return NULL; + } + if (GNUNET_YES != sepa_wire_validate (cls, + ret, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to validate SEPA data in %s\n", + sepa_wire_file); + GNUNET_free (sepa_wire_file); + json_decref (ret); + return NULL; + } + GNUNET_free (sepa_wire_file); + return ret; +} + + +/** + * Sign wire transfer details in the plugin-specific format. + * + * @param cls closure + * @param in wire transfer details in JSON format + * @param key private signing key to use + * @param salt salt to add + * @param[out] sig where to write the signature + * @return #GNUNET_OK on success + */ +static int +sepa_sign_wire_details (void *cls, + const json_t *in, + const struct TALER_MasterPrivateKeyP *key, + const struct GNUNET_HashCode *salt, + struct TALER_MasterSignatureP *sig) +{ + struct TALER_MasterWireDetailsPS wsd; + const char *sepa_name; + const char *iban; + const char *bic; + const char *type; + json_error_t err; + + if (0 != + json_unpack_ex ((json_t *) in, + &err, + 0 /* flags */, + "{s:s, s:s, s:s, s:s}", + "type", &type, + "name", &sepa_name, + "iban", &iban, + "bic", &bic)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to unpack JSON: %s (at %u)\n", + err.text, + err.position); + return GNUNET_SYSERR; + } + if (0 != strcmp (type, + "sepa")) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "`type' must be `sepa' for SEPA wire details\n"); + return GNUNET_SYSERR; + } + if (1 != validate_iban (iban)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "IBAN invalid in SEPA wire details\n"); + return GNUNET_SYSERR; + } + compute_purpose (sepa_name, + iban, + bic, + &wsd); + GNUNET_CRYPTO_eddsa_sign (&key->eddsa_priv, + &wsd.purpose, + &sig->eddsa_signature); + return GNUNET_OK; +} + + +/** * Prepare for exeuction of a wire transfer. * * @param cls the @e cls of this struct with the plugin-specific state @@ -499,22 +733,26 @@ libtaler_plugin_wire_sepa_init (void *cls) struct TALER_WIRE_Plugin *plugin; sc = GNUNET_new (struct SepaClosure); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", - "CURRENCY", - &sc->currency)) + if (NULL != cfg) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", - "CURRENCY"); - GNUNET_free (sc); - return NULL; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "CURRENCY", + &sc->currency)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "CURRENCY"); + GNUNET_free (sc); + return NULL; + } } - plugin = GNUNET_new (struct TALER_WIRE_Plugin); plugin->cls = sc; plugin->amount_round = &sepa_amount_round; + plugin->get_wire_details = &sepa_get_wire_details; + plugin->sign_wire_details = &sepa_sign_wire_details; plugin->wire_validate = &sepa_wire_validate; plugin->prepare_wire_transfer = &sepa_prepare_wire_transfer; plugin->prepare_wire_transfer_cancel = &sepa_prepare_wire_transfer_cancel; @@ -536,7 +774,7 @@ libtaler_plugin_wire_sepa_done (void *cls) struct TALER_WIRE_Plugin *plugin = cls; struct SepaClosure *sc = plugin->cls; - GNUNET_free (sc->currency); + GNUNET_free_non_null (sc->currency); GNUNET_free (sc); GNUNET_free (plugin); return NULL; diff --git a/src/wire/plugin_wire_template.c b/src/wire/plugin_wire_template.c index baf0ee7d5..46908c297 100644 --- a/src/wire/plugin_wire_template.c +++ b/src/wire/plugin_wire_template.c @@ -74,13 +74,36 @@ template_amount_round (void *cls, /** + * Obtain wire transfer details in the plugin-specific format + * from the configuration. + * + * @param cls closure + * @param cfg configuration with details about wire accounts + * @param account_name which section in the configuration should we parse + * @return NULL if @a cfg fails to have valid wire details for @a account_name + */ +static json_t * +template_get_wire_details (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *account_name) +{ + GNUNET_break (0); + return NULL; +} + + +/** * Check if the given wire format JSON object is correctly formatted * + * @param cls the @e cls of this struct with the plugin-specific state * @param wire the JSON wire format object + * @param master_pub public key of the exchange to verify against * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not */ static int -template_wire_validate (const json_t *wire) +template_wire_validate (void *cls, + const json_t *wire, + const struct TALER_MasterPublicKeyP *master_pub) { GNUNET_break (0); return GNUNET_SYSERR; @@ -149,6 +172,28 @@ template_execute_wire_transfer (void *cls, /** + * Sign wire transfer details in the plugin-specific format. + * + * @param cls closure + * @param in wire transfer details in JSON format + * @param key private signing key to use + * @param salt salt to add + * @param[out] sig where to write the signature + * @return #GNUNET_OK on success + */ +static int +template_sign_wire_details (void *cls, + const json_t *in, + const struct TALER_MasterPrivateKeyP *key, + const struct GNUNET_HashCode *salt, + struct TALER_MasterSignatureP *sig) +{ + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** * Abort execution of a wire transfer. For example, because we are * shutting down. Note that if an execution is aborted, it may or * may not still succeed. The caller MUST run @e @@ -197,12 +242,12 @@ libtaler_plugin_wire_template_init (void *cls) } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", + "exchange", "CURRENCY", &tc->currency)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", + "exchange", "CURRENCY"); GNUNET_free (tc->bank_uri); GNUNET_free (tc); @@ -212,6 +257,8 @@ libtaler_plugin_wire_template_init (void *cls) plugin = GNUNET_new (struct TALER_WIRE_Plugin); plugin->cls = tc; plugin->amount_round = &template_amount_round; + plugin->get_wire_details = &template_get_wire_details; + plugin->sign_wire_details = &template_sign_wire_details; plugin->wire_validate = &template_wire_validate; plugin->prepare_wire_transfer = &template_prepare_wire_transfer; plugin->prepare_wire_transfer_cancel = &template_prepare_wire_transfer_cancel; diff --git a/src/wire/plugin_wire_test.c b/src/wire/plugin_wire_test.c index 8a6f98ea2..d467b7bdd 100644 --- a/src/wire/plugin_wire_test.c +++ b/src/wire/plugin_wire_test.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016 GNUnet e.V. + Copyright (C) 2016 GNUnet e.V. & Inria 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 @@ -22,6 +22,7 @@ #include "platform.h" #include "taler_wire_plugin.h" #include "taler_bank_service.h" +#include "taler_signatures.h" /* only for HTTP status codes */ #include <microhttpd.h> @@ -34,20 +35,31 @@ struct TestClosure { /** - * Handle to the bank for sending funds to the bank. + * Which currency do we support? */ - struct TALER_BANK_Context *bank; + char *currency; /** - * Which currency do we support? + * URI of our bank. */ - char *currency; + char *bank_uri; + + /** + * Handle to the bank for sending funds to the bank. + */ + struct TALER_BANK_Context *bank; /** * Handle to the bank task, or NULL. */ struct GNUNET_SCHEDULER_Task *bt; + /** + * Number of the account that the exchange has at the bank for + * outgoing transfers. + */ + unsigned long long exchange_account_outgoing_no; + }; @@ -124,11 +136,9 @@ struct TALER_WIRE_ExecuteHandle * scheduler. * * @param cls our `struct TestClosure` - * @param tc scheduler context (unused) */ static void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *sct) +context_task (void *cls) { struct TestClosure *tc = cls; long timeout; @@ -171,7 +181,7 @@ context_task (void *cls, rs, ws, &context_task, - cls); + tc); GNUNET_NETWORK_fdset_destroy (rs); GNUNET_NETWORK_fdset_destroy (ws); } @@ -210,6 +220,13 @@ test_amount_round (void *cls, struct TestClosure *tc = cls; uint32_t delta; + if (NULL == tc->currency) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "CURRENCY"); + return GNUNET_SYSERR; /* not configured with currency */ + } if (0 != strcasecmp (amount->currency, tc->currency)) { @@ -226,27 +243,69 @@ test_amount_round (void *cls, /** + * Compute purpose for signing. + * + * @param account number of the account + * @param bank_uri URI of the bank + * @param[out] mp purpose to be signed + */ +static void +compute_purpose (uint64_t account, + const char *bank_uri, + struct TALER_MasterWireDetailsPS *wsd) +{ + struct GNUNET_HashContext *hc; + uint64_t n = GNUNET_htonll (account); + + wsd->purpose.size = htonl (sizeof (struct TALER_MasterWireDetailsPS)); + wsd->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_TEST_DETAILS); + hc = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hc, + "test", + strlen ("test") + 1); + GNUNET_CRYPTO_hash_context_read (hc, + &n, + sizeof (n)); + GNUNET_CRYPTO_hash_context_read (hc, + bank_uri, + strlen (bank_uri) + 1); + GNUNET_CRYPTO_hash_context_finish (hc, + &wsd->h_sepa_details); +} + + +/** * Check if the given wire format JSON object is correctly formatted. * Right now, the only thing we require is a field * "account_number" which must contain a positive 53-bit integer. * + * @param cls the @e cls of this struct with the plugin-specific state * @param wire the JSON wire format object + * @param master_pub public key of the exchange to verify against * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not */ static int -test_wire_validate (const json_t *wire) +test_wire_validate (void *cls, + const json_t *wire, + const struct TALER_MasterPublicKeyP *master_pub) { + struct TestClosure *tc = cls; json_error_t error; json_int_t account_no; + const char *bank_uri; + const char *sig_s; + struct TALER_MasterWireDetailsPS wsd; + struct TALER_MasterSignatureP sig; if (0 != json_unpack_ex ((json_t *) wire, &error, 0, - "{s:I}", - "account_number", &account_no)) + "{s:I, s:s}", + "account_number", &account_no, + "bank_uri", &bank_uri)) { - GNUNET_break (0); + GNUNET_break_op (0); return GNUNET_SYSERR; } if ( (account_no < 0) || @@ -255,10 +314,119 @@ test_wire_validate (const json_t *wire) GNUNET_break (0); return GNUNET_SYSERR; } + if ( (NULL != tc->bank_uri) && + (0 != strcmp (bank_uri, + tc->bank_uri)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire specifies bank URI %s, but this exchange only supports %s\n", + bank_uri, + tc->bank_uri); + return GNUNET_NO; + } + if (NULL == master_pub) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Skipping signature check as master public key not given\n"); + return GNUNET_OK; + } + if (0 != + json_unpack_ex ((json_t *) wire, + &error, + 0, + "{s:s}", + "sig", &sig_s)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Signature check required, but signature is missing\n"); + return GNUNET_NO; + } + compute_purpose (account_no, + bank_uri, + &wsd); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (sig_s, + strlen (sig_s), + &sig, + sizeof (sig))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_TEST_DETAILS, + &wsd.purpose, + &sig.eddsa_signature, + &master_pub->eddsa_pub)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } return GNUNET_YES; } +/** + * Obtain wire transfer details in the plugin-specific format + * from the configuration. + * + * @param cls closure + * @param cfg configuration with details about wire accounts + * @param account_name which section in the configuration should we parse + * @return NULL if @a cfg fails to have valid wire details for @a account_name + */ +static json_t * +test_get_wire_details (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *account_name) +{ + struct TestClosure *tc = cls; + char *test_wire_file; + json_error_t err; + json_t *ret; + + /* Fetch reply */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + account_name, + "TEST_RESPONSE_FILE", + &test_wire_file)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + account_name, + "TEST_RESPONSE_FILE"); + return NULL; + } + ret = json_load_file (test_wire_file, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse JSON in %s: %s (%s:%u)\n", + test_wire_file, + err.text, + err.source, + err.line); + GNUNET_free (test_wire_file); + return NULL; + } + if (GNUNET_YES != test_wire_validate (tc, + ret, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to validate TEST wire data in %s\n", + test_wire_file); + GNUNET_free (test_wire_file); + json_decref (ret); + return NULL; + } + GNUNET_free (test_wire_file); + return ret; +} + + GNUNET_NETWORK_STRUCT_BEGIN /** * Format we used for serialized transaction data. @@ -287,11 +455,9 @@ GNUNET_NETWORK_STRUCT_END * callback with the serialized state. * * @param cls the `struct TALER_WIRE_PrepareHandle` - * @param sct unused */ static void -do_prepare (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *sct) +do_prepare (void *cls) { struct TALER_WIRE_PrepareHandle *pth = cls; char *wire_enc; @@ -363,9 +529,11 @@ test_prepare_wire_transfer (void *cls, struct TALER_WIRE_PrepareHandle *pth; if (GNUNET_YES != - test_wire_validate (wire)) + test_wire_validate (tc, + wire, + NULL)) { - GNUNET_break (0); + GNUNET_break_op (0); return NULL; } pth = GNUNET_new (struct TALER_WIRE_PrepareHandle); @@ -406,27 +574,100 @@ test_prepare_wire_transfer_cancel (void *cls, * @param cls closure with the `struct TALER_WIRE_ExecuteHandle` * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request * 0 if the bank's reply is bogus (fails to follow the protocol) + * @param json detailed response from the HTTPD, or NULL if reply was not JSON */ static void execute_cb (void *cls, - unsigned int http_status) + unsigned int http_status, + json_t *json) { struct TALER_WIRE_ExecuteHandle *eh = cls; - char s[14]; + json_t *reason; + const char *emsg; + char *s; eh->aaih = NULL; - GNUNET_snprintf (s, - sizeof (s), - "%u", - http_status); + emsg = NULL; + if (NULL != json) + { + reason = json_object_get (json, + "reason"); + if (NULL != reason) + emsg = json_string_value (reason); + } + if (NULL != emsg) + GNUNET_asprintf (&s, + "%u (%s)", + http_status, + emsg); + else + GNUNET_asprintf (&s, + "%u", + http_status); eh->cc (eh->cc_cls, (MHD_HTTP_OK == http_status) ? GNUNET_OK : GNUNET_SYSERR, (MHD_HTTP_OK == http_status) ? NULL : s); + GNUNET_free (s); GNUNET_free (eh); } /** + * Sign wire transfer details in the plugin-specific format. + * + * @param cls closure + * @param in wire transfer details in JSON format + * @param key private signing key to use + * @param salt salt to add + * @param[out] sig where to write the signature + * @return #GNUNET_OK on success + */ +static int +test_sign_wire_details (void *cls, + const json_t *in, + const struct TALER_MasterPrivateKeyP *key, + const struct GNUNET_HashCode *salt, + struct TALER_MasterSignatureP *sig) +{ + struct TALER_MasterWireDetailsPS wsd; + const char *bank_uri; + const char *type; + json_int_t account; + json_error_t err; + + if (0 != + json_unpack_ex ((json_t *) in, + &err, + 0 /* flags */, + "{s:s, s:s, s:I}", + "type", &type, + "bank_uri", &bank_uri, + "account_number", &account)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to unpack JSON: %s (at %u)\n", + err.text, + err.position); + return GNUNET_SYSERR; + } + if (0 != strcmp (type, + "test")) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "`type' must be `test' for test wire details\n"); + return GNUNET_SYSERR; + } + compute_purpose (account, + bank_uri, + &wsd); + GNUNET_CRYPTO_eddsa_sign (&key->eddsa_priv, + &wsd.purpose, + &sig->eddsa_signature); + return GNUNET_OK; +} + + +/** * Execute a wire transfer. * * @param cls the @e cls of this struct with the plugin-specific state @@ -451,6 +692,12 @@ test_execute_wire_transfer (void *cls, json_int_t account_no; struct BufFormatP bf; + if (NULL == tc->bank) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Bank not initialized, cannot do transfers!\n"); + return NULL; /* not initialized with configuration, cannot do transfers */ + } if ( (buf_size <= sizeof (struct BufFormatP)) || ('\0' != buf[buf_size -1]) ) { @@ -471,7 +718,9 @@ test_execute_wire_transfer (void *cls, return NULL; } GNUNET_assert (GNUNET_YES == - test_wire_validate (wire)); + test_wire_validate (tc, + wire, + NULL)); if (0 != json_unpack_ex (wire, &error, @@ -482,13 +731,14 @@ test_execute_wire_transfer (void *cls, GNUNET_break (0); return NULL; } - + eh = GNUNET_new (struct TALER_WIRE_ExecuteHandle); eh->cc = cc; eh->cc_cls = cc_cls; eh->aaih = TALER_BANK_admin_add_incoming (tc->bank, &bf.wtid, &amount, + (uint64_t) tc->exchange_account_outgoing_no, (uint64_t) account_no, &execute_cb, eh); @@ -537,45 +787,63 @@ libtaler_plugin_wire_test_init (void *cls) struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct TestClosure *tc; struct TALER_WIRE_Plugin *plugin; - char *uri; - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-test", - "bank_uri", - &uri)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "wire-test", - "bank_uri"); - return NULL; - } tc = GNUNET_new (struct TestClosure); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", - "CURRENCY", - &tc->currency)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", - "CURRENCY"); - GNUNET_free (uri); - GNUNET_free (tc); - return NULL; - } - tc->bank = TALER_BANK_init (uri); - if (NULL == tc->bank) + if (NULL != cfg) { - GNUNET_break (0); - GNUNET_free (tc->currency); - GNUNET_free (tc); - return NULL; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-outgoing-test", + "BANK_URI", + &tc->bank_uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "wire-outgoing-test", + "BANK_URI"); + GNUNET_free (tc); + return NULL; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "wire-outgoing-test", + "EXCHANGE_ACCOUNT_NUMBER", + &tc->exchange_account_outgoing_no)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "wire-outgoing-test", + "EXCHANGE_ACCOUNT_NUMBER"); + GNUNET_free (tc->bank_uri); + GNUNET_free (tc); + return NULL; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "CURRENCY", + &tc->currency)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "CURRENCY"); + GNUNET_free (tc->bank_uri); + GNUNET_free (tc); + return NULL; + } + tc->bank = TALER_BANK_init (tc->bank_uri); + if (NULL == tc->bank) + { + GNUNET_break (0); + GNUNET_free (tc->currency); + GNUNET_free (tc->bank_uri); + GNUNET_free (tc); + return NULL; + } } - plugin = GNUNET_new (struct TALER_WIRE_Plugin); plugin->cls = tc; plugin->amount_round = &test_amount_round; + plugin->get_wire_details = &test_get_wire_details; + plugin->sign_wire_details = &test_sign_wire_details; plugin->wire_validate = &test_wire_validate; plugin->prepare_wire_transfer = &test_prepare_wire_transfer; plugin->prepare_wire_transfer_cancel = &test_prepare_wire_transfer_cancel; @@ -602,8 +870,13 @@ libtaler_plugin_wire_test_done (void *cls) GNUNET_SCHEDULER_cancel (tc->bt); tc->bt = NULL; } - TALER_BANK_fini (tc->bank); - GNUNET_free (tc->currency); + if (NULL != tc->bank) + { + TALER_BANK_fini (tc->bank); + tc->bank = NULL; + } + GNUNET_free_non_null (tc->currency); + GNUNET_free_non_null (tc->bank_uri); GNUNET_free (tc); GNUNET_free (plugin); return NULL; diff --git a/src/wire/test_sepa_wireformat.c b/src/wire/test_sepa_wireformat.c index edbe5bc45..cd31a971c 100644 --- a/src/wire/test_sepa_wireformat.c +++ b/src/wire/test_sepa_wireformat.c @@ -28,37 +28,37 @@ /* Valid SEPA data */ static const char * const valid_wire_str = "{ \"type\":\"SEPA\", \ -\"IBAN\":\"DE67830654080004822650\", \ +\"iban\":\"DE67830654080004822650\", \ \"name\":\"GNUnet e.V.\", \ \"bic\":\"GENODEF1SLR\", \ -\"r\":123456789, \ +\"salt\":\"123456789\", \ \"address\": \"foobar\"}"; /* IBAN has wrong country code */ static const char * const invalid_wire_str = "{ \"type\":\"SEPA\", \ -\"IBAN\":\"XX67830654080004822650\", \ +\"iban\":\"XX67830654080004822650\", \ \"name\":\"GNUnet e.V.\", \ \"bic\":\"GENODEF1SLR\", \ -\"r\":123456789, \ +\"salt\":\"123456789\", \ \"address\": \"foobar\"}"; /* IBAN has wrong checksum */ static const char * const invalid_wire_str2 = "{ \"type\":\"SEPA\", \ -\"IBAN\":\"DE67830654080004822651\", \ +\"iban\":\"DE67830654080004822651\", \ \"name\":\"GNUnet e.V.\", \ \"bic\":\"GENODEF1SLR\", \ -\"r\":123456789, \ +\"salt\":\"123456789\", \ \"address\": \"foobar\"}"; /* Unsupported wireformat type */ static const char * const unsupported_wire_str = "{ \"type\":\"unsupported\", \ -\"IBAN\":\"DE67830654080004822650\", \ +\"iban\":\"DE67830654080004822650\", \ \"name\":\"GNUnet e.V.\", \ \"bic\":\"GENODEF1SLR\", \ -\"r\":123456789, \ +\"salt\":\"123456789\", \ \"address\": \"foobar\"}"; @@ -77,7 +77,7 @@ main(int argc, NULL); cfg = GNUNET_CONFIGURATION_create (); GNUNET_CONFIGURATION_set_value_string (cfg, - "mint", + "exchange", "currency", "EUR"); plugin = TALER_WIRE_plugin_load (cfg, @@ -85,16 +85,24 @@ main(int argc, GNUNET_assert (NULL != plugin); (void) memset(&error, 0, sizeof(error)); GNUNET_assert (NULL != (wire = json_loads (unsupported_wire_str, 0, NULL))); - GNUNET_assert (GNUNET_YES != plugin->wire_validate (wire)); + GNUNET_assert (GNUNET_YES != plugin->wire_validate (NULL, + wire, + NULL)); json_decref (wire); GNUNET_assert (NULL != (wire = json_loads (invalid_wire_str, 0, NULL))); - GNUNET_assert (GNUNET_NO == plugin->wire_validate (wire)); + GNUNET_assert (GNUNET_NO == plugin->wire_validate (NULL, + wire, + NULL)); json_decref (wire); GNUNET_assert (NULL != (wire = json_loads (invalid_wire_str2, 0, NULL))); - GNUNET_assert (GNUNET_NO == plugin->wire_validate (wire)); + GNUNET_assert (GNUNET_NO == plugin->wire_validate (NULL, + wire, + NULL)); json_decref (wire); GNUNET_assert (NULL != (wire = json_loads (valid_wire_str, 0, &error))); - ret = plugin->wire_validate (wire); + ret = plugin->wire_validate (NULL, + wire, + NULL); json_decref (wire); TALER_WIRE_plugin_unload (plugin); GNUNET_CONFIGURATION_destroy (cfg); diff --git a/src/wire/test_wire_plugin.c b/src/wire/test_wire_plugin.c new file mode 100644 index 000000000..27b4366c1 --- /dev/null +++ b/src/wire/test_wire_plugin.c @@ -0,0 +1,196 @@ +/* + This file is part of TALER + (C) 2015, 2016 GNUnet e.V. and Inria + + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file wire/test_wire_plugin.c + * @brief Tests for wire plugins + * @author Christian Grothoff + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_wire_lib.h" +#include "taler_wire_plugin.h" +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> + + +/** + * Definitions for a test with a plugin. + */ +struct TestBlock { + + /** + * Name of the plugin to test. + */ + const char *plugin_name; + + /** + * JSON template expected by the plugin for an account definition. + */ + const char *json_proto; + +}; + + +/** + * List of plugins and (unsigned) JSON account definitions + * to use for the tests. + */ +static struct TestBlock tests[] = { + { "sepa", "{ \"type\":\"sepa\", \"iban\":\"DE67830654080004822650\", \"name\":\"GNUnet e.V.\", \"bic\":\"GENODEF1SLR\" }" }, + { "test", "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":42 }" }, + { NULL, NULL } +}; + + +/** + * Private key used to sign wire details. + */ +static struct TALER_MasterPrivateKeyP priv_key; + +/** + * Public key matching #priv_key. + */ +static struct TALER_MasterPublicKeyP pub_key; + +/** + * Our configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + + +/** + * Run the test. + * + * @param name of the test + * @param plugin plugin to test + * @param wire wire details for testing + * @return #GNUNET_OK on success + */ +static int +run_test (const char *name, + struct TALER_WIRE_Plugin *plugin, + json_t *wire) +{ + struct GNUNET_HashCode salt; + struct TALER_MasterSignatureP sig; + json_t *lwire; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &salt, + sizeof (salt)); + if (GNUNET_OK != + plugin->sign_wire_details (plugin->cls, + wire, + &priv_key, + &salt, + &sig)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_object_set_new (wire, + "salt", + GNUNET_JSON_from_data (&salt, + sizeof (salt))); + json_object_set_new (wire, + "sig", + GNUNET_JSON_from_data (&sig, + sizeof (sig))); + if (GNUNET_OK != + plugin->wire_validate (plugin->cls, + wire, + &pub_key)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* load wire details from file */ + lwire = plugin->get_wire_details (plugin->cls, + cfg, + name); + if (NULL == lwire) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + plugin->wire_validate (plugin->cls, + lwire, + &pub_key)) + { + GNUNET_break (0); + json_decref (lwire); + return GNUNET_SYSERR; + } + json_decref (lwire); + return GNUNET_OK; +} + + +int +main (int argc, + const char *const argv[]) +{ + json_t *wire; + int ret; + struct TALER_WIRE_Plugin *plugin; + const struct TestBlock *test; + unsigned int i; + struct GNUNET_CRYPTO_EddsaPrivateKey *pk; + + GNUNET_log_setup ("test-wire-plugin", + "WARNING", + NULL); + cfg = GNUNET_CONFIGURATION_create (); + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_load (cfg, + "test_wire_plugin.conf")); + pk = GNUNET_CRYPTO_eddsa_key_create_from_file ("test_wire_plugin_key.priv"); + priv_key.eddsa_priv = *pk; + GNUNET_free (pk); + GNUNET_CRYPTO_eddsa_key_get_public (&priv_key.eddsa_priv, + &pub_key.eddsa_pub); + ret = GNUNET_OK; + for (i=0;NULL != (test = &tests[i])->plugin_name;i++) + { + plugin = TALER_WIRE_plugin_load (cfg, + test->plugin_name); + GNUNET_assert (NULL != plugin); + wire = json_loads (test->json_proto, 0, NULL); + GNUNET_assert (NULL != wire); + ret = run_test (test->plugin_name, plugin, wire); + json_decref (wire); + TALER_WIRE_plugin_unload (plugin); + if (GNUNET_OK != ret) + { + fprintf (stdout, + "%s FAILED\n", + test->plugin_name); + break; + } + else + { + fprintf (stdout, + "%s PASS\n", + test->plugin_name); + } + } + GNUNET_CONFIGURATION_destroy (cfg); + if (GNUNET_NO == ret) + return 1; + return 0; +} diff --git a/src/wire/test_wire_plugin.conf b/src/wire/test_wire_plugin.conf new file mode 100644 index 000000000..ece816954 --- /dev/null +++ b/src/wire/test_wire_plugin.conf @@ -0,0 +1,21 @@ +# This file is in the public domain. +# +[test] +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +TEST_RESPONSE_FILE = test_wire_plugin_test.json + +[sepa] +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +SEPA_RESPONSE_FILE = test_wire_plugin_sepa.json + + +[wire-outgoing-test] +# For transfers made by the exchange, we need to know +# the URI of the bank (where the /admin/add/incoming API +# is avaialble). +BANK_URI = http://localhost/ + +[exchange] +CURRENCY = "EUR" diff --git a/src/wire/test_wire_plugin_key.priv b/src/wire/test_wire_plugin_key.priv new file mode 100644 index 000000000..26b4f26f6 --- /dev/null +++ b/src/wire/test_wire_plugin_key.priv @@ -0,0 +1 @@ +?Sgb@Js;%aKȉs_Hў
\ No newline at end of file diff --git a/src/wire/test_wire_plugin_sepa.json b/src/wire/test_wire_plugin_sepa.json new file mode 100644 index 000000000..175345f0c --- /dev/null +++ b/src/wire/test_wire_plugin_sepa.json @@ -0,0 +1,8 @@ +{ + "salt": "32V01R7K4T02S74PZZMVXRQ1K7FR948RBNB9BJ5Z101HEQFH7CW7J82006GY3BPTGQ4FM775PSSRD3K9MY97HSNVVCGEVBPVSAQ2710", + "type": "sepa", + "iban": "DE67830654080004822650", + "sig": "K48GPPM715ZXX0DC597WESD5ECT3R0B3TAFQMB68SBF4K5CZ5KCE9NESN1JX412SPZ82PSV7JAPVJFXDDTZ63YV4295S5RC28E4221G", + "name": "GNUnet e.V.", + "bic": "GENODEF1SLR" +}
\ No newline at end of file diff --git a/src/wire/test_wire_plugin_test.json b/src/wire/test_wire_plugin_test.json new file mode 100644 index 000000000..6fe6b2359 --- /dev/null +++ b/src/wire/test_wire_plugin_test.json @@ -0,0 +1,7 @@ +{ + "type": "test", + "bank_uri": "http://localhost/", + "sig": "KX1CMHNFH1WE10244AEF07AXHJCF9PZDZVNZBC9P4EJEQ1MH1Y3C2TWF08VTQMK4N5TCV0V1VTGWSV0WB8TB9YQRZW87F5A6KCEZ81R", + "account_number": 42, + "salt": "EZV905MQPVAZEMGC6SEZQF2Z75P6ZKTN8TX00JHN11S7J81DQ78G8Z551K6TGR9WHPP0JW1X9J9X9CVRY48JTHBCP6Q4XKJ6R2G18G0" +}
\ No newline at end of file diff --git a/src/wire/wire-sepa.conf b/src/wire/wire-sepa.conf new file mode 100644 index 000000000..7321a2be6 --- /dev/null +++ b/src/wire/wire-sepa.conf @@ -0,0 +1,10 @@ +# Configuration for SEPA wire plugin. + +[wire-incoming-sepa] +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +SEPA_RESPONSE_FILE = ${TALER_CONFIG_HOME}/sepa.json + +[wire-outgoing-sepa] +# This section should contain the options required for making outgoing +# SEPA transfers. Not yet supported (need libebics). diff --git a/src/wire/wire-test.conf b/src/wire/wire-test.conf new file mode 100644 index 000000000..98e486acb --- /dev/null +++ b/src/wire/wire-test.conf @@ -0,0 +1,16 @@ +# This file is in the public domain. +# +[wire-incoming-test] +# This is the response we give out for the /wire request. It provides +# wallets with the bank information for transfers to the exchange. +TEST_RESPONSE_FILE = ${TALER_CONFIG_HOME}/test.json + +[wire-outgoing-test] +# For outgoing transfers, we need to know the exchange's +# account number at the bank. +EXCHANGE_ACCOUNT_NUMBER = 2 + +# For transfers made by the exchange, we need to know +# the URI of the bank (where the /admin/add/incoming API +# is avaialble). +# BANK_URI = https://bank.demo.taler.net/ |