diff options
Diffstat (limited to 'src')
165 files changed, 14521 insertions, 7069 deletions
diff --git a/src/authorization/Makefile.am b/src/authorization/Makefile.am index cfcd89e..5d2854d 100644 --- a/src/authorization/Makefile.am +++ b/src/authorization/Makefile.am @@ -21,6 +21,8 @@ pkgdata_DATA = \ EXTRA_DIST = \ $(pkgdata_DATA) \ + $(cfg_DATA) \ + $(bin_SCRIPTS) \ iban.h iban.c @@ -33,7 +35,9 @@ bin_PROGRAMS = \ anastasis-helper-authorization-iban bin_SCRIPTS = \ - anastasis-authorization-email.sh + anastasis-authorization-email.sh \ + anastasis-authorization-sms.sh \ + anastasis-authorization-post.sh anastasis_helper_authorization_iban_SOURCES = \ anastasis-helper-authorization-iban.c @@ -69,7 +73,8 @@ plugin_LTLIBRARIES = \ libanastasis_plugin_authorization_file.la \ libanastasis_plugin_authorization_iban.la \ libanastasis_plugin_authorization_post.la \ - libanastasis_plugin_authorization_sms.la + libanastasis_plugin_authorization_sms.la \ + libanastasis_plugin_authorization_totp.la libanastasis_plugin_authorization_file_la_SOURCES = \ @@ -95,6 +100,7 @@ libanastasis_plugin_authorization_email_la_LIBADD = \ libanastasis_plugin_authorization_email_la_LDFLAGS = \ $(ANASTASIS_PLUGIN_LDFLAGS) \ $(top_builddir)/src/stasis/libanastasisdb.la \ + $(top_builddir)/src/util/libanastasisutil.la \ -ltalerjson \ -ltalermhd \ -ltalerutil \ @@ -127,6 +133,7 @@ libanastasis_plugin_authorization_post_la_LIBADD = \ libanastasis_plugin_authorization_post_la_LDFLAGS = \ $(ANASTASIS_PLUGIN_LDFLAGS) \ $(top_builddir)/src/stasis/libanastasisdb.la \ + $(top_builddir)/src/util/libanastasisutil.la \ -ltalerjson \ -ltalermhd \ -ltalerutil \ @@ -143,6 +150,25 @@ libanastasis_plugin_authorization_sms_la_LIBADD = \ libanastasis_plugin_authorization_sms_la_LDFLAGS = \ $(ANASTASIS_PLUGIN_LDFLAGS) \ $(top_builddir)/src/stasis/libanastasisdb.la \ + $(top_builddir)/src/util/libanastasisutil.la \ + -ltalerjson \ + -ltalermhd \ + -ltalerutil \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + -lmicrohttpd \ + $(XLIB) + + +libanastasis_plugin_authorization_totp_la_SOURCES = \ + anastasis_authorization_plugin_totp.c +libanastasis_plugin_authorization_totp_la_LIBADD = \ + $(LTLIBINTL) +libanastasis_plugin_authorization_totp_la_LDFLAGS = \ + $(ANASTASIS_PLUGIN_LDFLAGS) \ + $(top_builddir)/src/stasis/libanastasisdb.la \ + $(top_builddir)/src/util/libanastasisutil.la \ -ltalerjson \ -ltalermhd \ -ltalerutil \ @@ -150,4 +176,5 @@ libanastasis_plugin_authorization_sms_la_LDFLAGS = \ -lgnunetutil \ -ljansson \ -lmicrohttpd \ + -lgcrypt \ $(XLIB) diff --git a/src/authorization/anastasis-authorization-email.sh b/src/authorization/anastasis-authorization-email.sh index ae3ecf7..738aaf0 100755 --- a/src/authorization/anastasis-authorization-email.sh +++ b/src/authorization/anastasis-authorization-email.sh @@ -1,2 +1,3 @@ #!/bin/sh +# This file is in the public domain. exec mail -s "Anastasis" -r noreply "$1" diff --git a/src/authorization/anastasis-authorization-post.sh b/src/authorization/anastasis-authorization-post.sh new file mode 100755 index 0000000..66255ea --- /dev/null +++ b/src/authorization/anastasis-authorization-post.sh @@ -0,0 +1,196 @@ +#!/bin/bash +# This file is in the public domain. +set -eu + +# Check shared secrets +if [ -x "$PINGEN_CLIENT_ID" ] +then + echo "PINGEN_CLIENT_ID not sent in environment" + exit 1 +fi +if [ -x "$PINGEN_CLIENT_SECRET" ] +then + echo "PINGEN_CLIENT_SECRET not sent in environment" + exit 1 +fi +if [ -x "$PINGEN_ORG_ID" ] +then + echo "PINGEN_ORG_ID not sent in environment" + exit 1 +fi + +ENDPOINT="https://api.pingen.com" +LOGS="$PWD/authorization-post.log" + +MESSAGE=$(cat -) +DATE=$(date +%F) +ADDR="$1" +NAME=$(echo $ADDR | jq -r .full_name) +STREET=$(echo $ADDR | jq -r .street) + +LNUMBER=$(echo $STREET | awk '{print $NF}') +FNUMBER=$(echo $STREET | awk '{print $1}') +case $LNUMBER in + ''|*[!0-9]*) + case $FNUMBER in + ''|*[!0-9]*) + NUMBER=0 + ;; + *) + NUMBER=$FNUMBER + ;; + esac + ;; + *) + NUMBER=$LNUMBER + ;; +esac + + +CITY=$(echo $ADDR | jq -r .city) +POSTCODE=$(echo $ADDR | jq -r .postcode) +COUNTRY=$(echo $ADDR | jq -r .country) + +MYDIR=$(mktemp -d /tmp/authorization-post-XXXXXX) +cd "$MYDIR" +cat - | sed -e "s/%NAME%/$NAME/g" \ + -e "s/%STREET%/$STREET/g" \ + -e "s/%POSTCODE%/$POSTCODE/g" \ + -e "s/%CITY%/$CITY/g" \ + -e "s/%COUNTRY%/$COUNTRY/g" \ + -e "s/%MESSAGE%/$MESSAGE/g" > input.tex <<EOF +\NeedsTeXFormat{LaTeX2e} +\documentclass[fontsize=11pt,a4paper]{scrlttr2} +\makeatletter +\KOMAoptions{foldmarks=off} +%\@setplength{toaddrvpos}{30mm} +%\@setplength{toaddrhpos}{130mm} +%\@setplength{sigbeforevskip}{10mm} +\makeatother +\setkomavar{subject}{Anastasis Recovery} +%\setkomavar{fromname}{Anastasis SARL} +\setkomavar{signature}{Anastasis SARL} +\date{\today} +%\address{Anastasis SARL \\\\ 7 rue de Mondorf \\\\ 5431 Erpeldange} +%\signature{Anastasis SARL} +\begin{document} +\begin{letter}{\ \ %NAME% \\\\ \ \ %STREET% \\\\ \ \ %POSTCODE% %CITY% \\\\ \ \ %COUNTRY% } +\opening{To whom it may concern,} +%MESSAGE% +\closing{Best regards} +\end{letter} +\end{document} +EOF +pdflatex input.tex > /dev/null 2> /dev/null + +REPLY=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "client_id=$PINGEN_CLIENT_ID" \ + --data-urlencode "client_secret=$PINGEN_CLIENT_SECRET" \ + --data-urlencode "scope=letter" \ + https://identity.pingen.com/auth/access-tokens) + +ACCESS_TOKEN=$(echo $REPLY | jq -r .access_token) + +REPLY=$(curl -s \ + -X GET "$ENDPOINT/file-upload" \ + -H "Authorization: Bearer $ACCESS_TOKEN") +ATTRS=$(echo $REPLY | jq .data.attributes) +UPLOAD_URL=$(echo $ATTRS | jq -r .url) +URL_SIG=$(echo $ATTRS | jq -r .url_signature) + +curl -s -X PUT -T input.pdf "$UPLOAD_URL" + + +RECIPIENT="$(jq -n ' + { + name: $NAME, + street: $STREET, + number: $NUMBER, + city: $CITY, + zip: $POSTCODE, + country: $COUNTRY, + }' \ + --arg NAME "$NAME" \ + --arg STREET "$STREET" \ + --arg NUMBER "$NUMBER" \ + --arg CITY "$CITY" \ + --arg POSTCODE "$POSTCODE" \ + --arg COUNTRY "$COUNTRY" \ + )" + +SENDER="$(jq -n ' + { + name: "Anastasis SARL", + street: "Rue de Mondorf", + number: "7", + zip: "5421", + city: "Erpeldange", + country: "LU" + }' + )" + +REQUEST="$(jq -n ' + { data: { + type: "letters", + attributes: { + file_original_name: "input.pdf", + file_url: $UPLOAD_URL, + file_url_signature: $URL_SIG, + address_position: "left", + delivery_product: "cheap", + print_mode: "duplex", + auto_send: true, + print_spectrum: "grayscale" + } } + }' \ + --argjson RECIPIENT "$RECIPIENT" \ + --argjson SENDER "$SENDER" \ + --arg UPLOAD_URL "$UPLOAD_URL" \ + --arg URL_SIG "$URL_SIG" \ + )" + +STATUS=$(curl -s --request POST \ + --url "$ENDPOINT/organisations/${PINGEN_ORG_ID}/letters" \ + --header 'Content-Type: application/vnd.api+json' \ + --header "Authorization: Bearer $ACCESS_TOKEN" \ + -d "$REQUEST" \ + -o "$MYDIR/final-reply.txt" \ + -w "%{http_code}" -s) +cat "$MYDIR/final-reply.txt" >> "$LOGS" +case $STATUS in + 201) + ;; + *) + echo "Failed to add letter: $STATUS" >> "$LOGS" + echo "$REPLY" + exit 1; + ;; +esac +LETTER_ID=$(cat "$MYDIR/final-reply.txt" | jq -r .data.id) +REPLY=$MYDIR/delete-reply.txt +STATUS=409 +sleep 1; +while test "$STATUS" = 409; +do + STATUS=$(curl -s --request DELETE \ + --url "$ENDPOINT/organisations/$PINGEN_ORG_ID/letters/$LETTER_ID" \ + --header "Authorization: Bearer $ACCESS_TOKEN" \ + -o "$REPLY" \ + -w "%{http_code}" -s) + case $STATUS in + 204) + cat "$REPLY" >> "$LOGS" + ;; + 409) + # Happens, likely still in processing... + ;; + *) + echo "Failed to delete letter: $STATUS" >> "$LOGS" + ;; + esac +done + +rm -r "$MYDIR" + +exit 0 diff --git a/src/authorization/anastasis-authorization-sms.sh b/src/authorization/anastasis-authorization-sms.sh new file mode 100755 index 0000000..c3b1055 --- /dev/null +++ b/src/authorization/anastasis-authorization-sms.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# This file is in the public domain. +set -eu + +# Check shared secrets +if [ -x "$TELESIGN_AUTH_TOKEN" ] +then + echo "TELESIGN_AUTH_TOKEN not sent in environment" + exit 1 +fi + +MESSAGE=$(cat -) +TMPFILE=$(mktemp /tmp/sms-loggingXXXXXX) +STATUS=$(curl --request POST \ + --url https://rest-api.telesign.com/v1/messaging \ + --header 'authorization: Basic $TELESIGN_AUTH_TOKEN' \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data account_livecycle_event=transact \ + --data "message=$MESSAGE" \ + --data message_type=OTP \ + --data "phone_number=$1" \ + -w "%{http_code}" -s -o $TMPFILE) +case $STATUS in + 200|203|250|290|291|295) + exit 0; + ;; + *) + exit 1; + ;; +esac +exit 1 diff --git a/src/authorization/anastasis_authorization_plugin.c b/src/authorization/anastasis_authorization_plugin.c index c557aa3..7e25c03 100644 --- a/src/authorization/anastasis_authorization_plugin.c +++ b/src/authorization/anastasis_authorization_plugin.c @@ -3,7 +3,7 @@ Copyright (C) 2015, 2016, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY diff --git a/src/authorization/anastasis_authorization_plugin_email.c b/src/authorization/anastasis_authorization_plugin_email.c index 0eefcc5..7fc97e7 100644 --- a/src/authorization/anastasis_authorization_plugin_email.c +++ b/src/authorization/anastasis_authorization_plugin_email.c @@ -3,7 +3,7 @@ Copyright (C) 2019-2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -220,7 +220,7 @@ email_validate (void *cls, { if (MHD_NO == TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_EMAIL_INVALID, NULL)) return GNUNET_SYSERR; @@ -296,8 +296,12 @@ email_done_cb (void *cls, { struct ANASTASIS_AUTHORIZATION_State *as = cls; - as->child = NULL; as->cwh = NULL; + if (NULL != as->child) + { + GNUNET_OS_process_destroy (as->child); + as->child = NULL; + } as->pst = type; as->exit_code = exit_code; MHD_resume_connection (as->connection); @@ -310,20 +314,17 @@ email_done_cb (void *cls, * I.e. start to send SMS or e-mail or launch video identification. * * @param as authorization state - * @param timeout how long do we have to produce a reply * @param connection HTTP client request (for queuing response, such as redirection to video portal) * @return state of the request */ -static enum ANASTASIS_AUTHORIZATION_Result -email_process (struct ANASTASIS_AUTHORIZATION_State *as, - struct GNUNET_TIME_Absolute timeout, - struct MHD_Connection *connection) +static enum ANASTASIS_AUTHORIZATION_ChallengeResult +email_challenge (struct ANASTASIS_AUTHORIZATION_State *as, + struct MHD_Connection *connection) { MHD_RESULT mres; const char *mime; const char *lang; - (void) timeout; mime = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT); @@ -348,8 +349,8 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED, "pipe"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, p, @@ -367,27 +368,19 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED, "exec"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } pipe_stdin = GNUNET_DISK_pipe_detach_end (p, GNUNET_DISK_PIPE_END_WRITE); GNUNET_assert (NULL != pipe_stdin); GNUNET_DISK_pipe_close (p); - { - char *tpk; - - tpk = GNUNET_STRINGS_data_to_string_alloc ( - &as->truth_uuid, - sizeof (as->truth_uuid)); - GNUNET_asprintf (&as->msg, - get_message (as->ctx->messages, - connection, - "body"), - (unsigned long long) as->code, - tpk); - GNUNET_free (tpk); - } + GNUNET_asprintf (&as->msg, + get_message (as->ctx->messages, + connection, + "body"), + ANASTASIS_pin2s (as->code), + ANASTASIS_CRYPTO_uuid2s (&as->truth_uuid)); { const char *off = as->msg; @@ -409,8 +402,8 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED, "write"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } as->msg_off += ret; off += ret; @@ -423,14 +416,14 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, as); as->connection = connection; MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; } if (NULL != as->cwh) { /* Spurious call, why are we here? */ GNUNET_break (0); MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; } if ( (GNUNET_OS_PROCESS_EXITED != as->pst) || (0 != as->exit_code) ) @@ -447,8 +440,8 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_EMAIL_HELPER_COMMAND_FAILED, es); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } /* Build HTTP response */ @@ -471,12 +464,9 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, user = GNUNET_strndup (as->email, len); resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint ( - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)), - GNUNET_JSON_pack_string ("detail", + GNUNET_JSON_pack_string ("challenge_type", + "TAN_SENT"), + GNUNET_JSON_pack_string ("tan_address_hint", user)); GNUNET_free (user); } @@ -502,12 +492,12 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, "text/plain")); } mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_SUCCESS; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS; } } @@ -606,7 +596,7 @@ libanastasis_plugin_authorization_email_init (void *cls) plugin->cls = ctx; plugin->validate = &email_validate; plugin->start = &email_start; - plugin->process = &email_process; + plugin->challenge = &email_challenge; plugin->cleanup = &email_cleanup; if (GNUNET_OK != diff --git a/src/authorization/anastasis_authorization_plugin_file.c b/src/authorization/anastasis_authorization_plugin_file.c index 66dbbe1..2f4fcb4 100644 --- a/src/authorization/anastasis_authorization_plugin_file.c +++ b/src/authorization/anastasis_authorization_plugin_file.c @@ -3,7 +3,7 @@ Copyright (C) 2019 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -86,7 +86,7 @@ file_validate (void *cls, (void) cls; if (NULL == data) - return GNUNET_NO; + return GNUNET_SYSERR; filename = GNUNET_STRINGS_data_to_string_alloc (data, data_length); flag = false; @@ -100,7 +100,7 @@ file_validate (void *cls, } } if (flag) - return GNUNET_NO; + return GNUNET_SYSERR; GNUNET_free (filename); return GNUNET_OK; } @@ -161,14 +161,12 @@ file_start (void *cls, * I.e. start to send SMS or e-mail or launch video identification. * * @param as authorization state - * @param timeout how long do we have to produce a reply * @param connection HTTP client request (for queuing response, such as redirection to video portal) * @return state of the request */ -static enum ANASTASIS_AUTHORIZATION_Result -file_process (struct ANASTASIS_AUTHORIZATION_State *as, - struct GNUNET_TIME_Absolute timeout, - struct MHD_Connection *connection) +static enum ANASTASIS_AUTHORIZATION_ChallengeResult +file_challenge (struct ANASTASIS_AUTHORIZATION_State *as, + struct MHD_Connection *connection) { const char *mime; const char *lang; @@ -201,8 +199,8 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as, resp); MHD_destroy_response (resp); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } /* print challenge code to file */ @@ -221,8 +219,8 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as, resp); MHD_destroy_response (resp); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } GNUNET_break (0 == fclose (f)); } @@ -235,6 +233,8 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as, "application/json")) { resp = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_string ("challenge_type", + "FILE_WRITTEN"), GNUNET_JSON_pack_string ("filename", as->filename)); } @@ -260,12 +260,12 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as, MHD_RESULT mres; mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_SUCCESS; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS; } } } @@ -304,7 +304,7 @@ libanastasis_plugin_authorization_file_init (void *cls) plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES; plugin->validate = &file_validate; plugin->start = &file_start; - plugin->process = &file_process; + plugin->challenge = &file_challenge; plugin->cleanup = &file_cleanup; return plugin; } diff --git a/src/authorization/anastasis_authorization_plugin_iban.c b/src/authorization/anastasis_authorization_plugin_iban.c index 7717770..92b4565 100644 --- a/src/authorization/anastasis_authorization_plugin_iban.c +++ b/src/authorization/anastasis_authorization_plugin_iban.c @@ -3,7 +3,7 @@ Copyright (C) 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -209,7 +209,7 @@ iban_validate (void *cls, GNUNET_free (iban_number); if (MHD_NO == TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_IBAN_INVALID, emsg)) { @@ -293,105 +293,13 @@ bank_event_cb (void *cls, } GNUNET_free (amount_s); } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "IBAN event triggers resumption of request handling\n"); MHD_resume_connection (as->connection); as->trigger (as->trigger_cls); } -/** - * Respond with instructions to the user how to - * satisfy the challenge. - * - * @param as our state - * @param connection connection to respond on - * @return state of the request - */ -static enum ANASTASIS_AUTHORIZATION_Result -respond_with_challenge (struct ANASTASIS_AUTHORIZATION_State *as, - struct MHD_Connection *connection) -{ - struct IBAN_Context *ctx = as->ctx; - const char *mime; - const char *lang; - MHD_RESULT mres; - - mime = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_ACCEPT); - if (NULL == mime) - mime = "text/plain"; - lang = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_ACCEPT_LANGUAGE); - if (NULL == lang) - lang = "en"; - - /* Build HTTP response */ - { - struct MHD_Response *resp; - - if (TALER_MHD_xmime_matches (mime, - "application/json")) - { - char subject[64]; - - GNUNET_snprintf (subject, - sizeof (subject), - "Anastasis %llu", - (unsigned long long) as->code); - resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_string ("method", - "iban"), - GNUNET_JSON_pack_bool ("async", - true), - GNUNET_JSON_pack_uint64 ("answer_code", - as->code), - GNUNET_JSON_pack_object_steal ( - "details", - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("challenge_amount", - &ctx->expected_amount), - GNUNET_JSON_pack_string ("credit_iban", - ctx->business_iban), - GNUNET_JSON_pack_string ("business_name", - ctx->business_name), - GNUNET_JSON_pack_string ("wire_transfer_subject", - subject)))); - } - else - { - size_t reply_len; - char *reply; - - reply_len = GNUNET_asprintf (&reply, - get_message (ctx->messages, - connection, - "instructions"), - TALER_amount2s (&ctx->expected_amount), - ctx->business_name, - ctx->business_iban, - (unsigned long long) as->code); - resp = MHD_create_response_from_buffer (reply_len, - reply, - MHD_RESPMEM_MUST_COPY); - GNUNET_free (reply); - TALER_MHD_add_global_headers (resp); - GNUNET_break (MHD_YES == - MHD_add_response_header (resp, - MHD_HTTP_HEADER_CONTENT_TYPE, - "text/plain")); - } - mres = MHD_queue_response (connection, - MHD_HTTP_ACCEPTED, - resp); - MHD_destroy_response (resp); - if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_SUCCESS; - } -} - - #include "iban.c" @@ -461,12 +369,14 @@ test_wire_transfers (struct ANASTASIS_AUTHORIZATION_State *as) struct ANASTASIS_DatabasePlugin *db = ctx->ac->db; enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Absolute limit; + struct GNUNET_TIME_Timestamp limit; now = GNUNET_TIME_absolute_get (); - limit = GNUNET_TIME_absolute_subtract (now, - CODE_VALIDITY_PERIOD); - (void) GNUNET_TIME_round_abs (&limit); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Testing for wire transfers\n"); + limit = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_subtract (now, + CODE_VALIDITY_PERIOD)); qs = db->test_auth_iban_payment ( db->cls, as->iban_number, @@ -482,15 +392,13 @@ test_wire_transfers (struct ANASTASIS_AUTHORIZATION_State *as) MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, NULL)) - ? WTS_FAILED_WITH_REPLY - : WTS_FAILED_WITHOUT_REPLY; + ? WTS_FAILED_WITH_REPLY + : WTS_FAILED_WITHOUT_REPLY; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return WTS_NOT_READY; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Marking IBAN challenge as satisfied!\n"); qs = db->mark_challenge_code_satisfied ( db->cls, &as->truth_uuid, @@ -501,26 +409,124 @@ test_wire_transfers (struct ANASTASIS_AUTHORIZATION_State *as) /** + * Respond with instructions to the user how to + * satisfy the challenge. + * + * @param as authorization state + * @param connection HTTP client request (for queuing response, such as redirection to video portal) + * @return state of the request + */ +static enum ANASTASIS_AUTHORIZATION_ChallengeResult +iban_challenge (struct ANASTASIS_AUTHORIZATION_State *as, + struct MHD_Connection *connection) +{ + struct IBAN_Context *ctx = as->ctx; + const char *mime; + const char *lang; + MHD_RESULT mres; + + mime = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT); + if (NULL == mime) + mime = "text/plain"; + lang = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_LANGUAGE); + if (NULL == lang) + lang = "en"; + + /* Build HTTP response */ + { + struct MHD_Response *resp; + + if (TALER_MHD_xmime_matches (mime, + "application/json")) + { + char subject[64]; + + GNUNET_snprintf (subject, + sizeof (subject), + "Anastasis %llu", + (unsigned long long) as->code); + resp = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_string ("challenge_type", + "IBAN_WIRE"), + GNUNET_JSON_pack_object_steal ( + "wire_details", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ( + "answer_code", + as->code), + TALER_JSON_pack_amount ( + "challenge_amount", + &ctx->expected_amount), + GNUNET_JSON_pack_string ( + "credit_iban", + ctx->business_iban), + GNUNET_JSON_pack_string ( + "business_name", + ctx->business_name), + GNUNET_JSON_pack_string ( + "wire_transfer_subject", + subject)))); + } + else + { + size_t reply_len; + char *reply; + + reply_len = GNUNET_asprintf (&reply, + get_message (ctx->messages, + connection, + "instructions"), + TALER_amount2s (&ctx->expected_amount), + ctx->business_name, + ctx->business_iban, + (unsigned long long) as->code); + resp = MHD_create_response_from_buffer (reply_len, + reply, + MHD_RESPMEM_MUST_COPY); + GNUNET_free (reply); + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + } + mres = MHD_queue_response (connection, + MHD_HTTP_OK, + resp); + MHD_destroy_response (resp); + if (MHD_YES != mres) + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS; + } +} + + +/** * Begin issuing authentication challenge to user based on @a data. * I.e. start to send IBAN or e-mail or launch video identification. * * @param as authorization state * @param timeout how long do we have to produce a reply + * @param challenge_response hash of the challenge response, or NULL * @param connection HTTP client request (for queuing response, such as redirection to video portal) * @return state of the request */ -static enum ANASTASIS_AUTHORIZATION_Result -iban_process (struct ANASTASIS_AUTHORIZATION_State *as, - struct GNUNET_TIME_Absolute timeout, - struct MHD_Connection *connection) +static enum ANASTASIS_AUTHORIZATION_SolveResult +iban_solve (struct ANASTASIS_AUTHORIZATION_State *as, + struct GNUNET_TIME_Absolute timeout, + const struct GNUNET_HashCode *challenge_response, + struct MHD_Connection *connection) { struct IBAN_Context *ctx = as->ctx; struct ANASTASIS_DatabasePlugin *db = ctx->ac->db; MHD_RESULT mres; enum GNUNET_DB_QueryStatus qs; - struct MHD_Response *resp; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_TIME_Absolute after; + struct GNUNET_TIME_Timestamp after; if (NULL == as->eh) { @@ -530,10 +536,6 @@ iban_process (struct ANASTASIS_AUTHORIZATION_State *as, .code = GNUNET_htonll (as->code) }; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Subscribing to events for code %llu from %s\n", - (unsigned long long) as->code, - as->iban_number); GNUNET_CRYPTO_hash (as->iban_number, strlen (as->iban_number), &espec.debit_iban_hash); @@ -544,9 +546,9 @@ iban_process (struct ANASTASIS_AUTHORIZATION_State *as, &bank_event_cb, as); } - after = GNUNET_TIME_absolute_subtract (now, - CODE_VALIDITY_PERIOD); - (void) GNUNET_TIME_round_abs (&after); + after = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_subtract (now, + CODE_VALIDITY_PERIOD)); qs = db->test_challenge_code_satisfied (db->cls, &as->truth_uuid, as->code, @@ -555,45 +557,54 @@ iban_process (struct ANASTASIS_AUTHORIZATION_State *as, { case GNUNET_DB_STATUS_HARD_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR: - resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, - "test_challenge_code_satisfied"); - mres = MHD_queue_response (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - resp); - MHD_destroy_response (resp); + mres = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "test challenge code satisfied"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: switch (test_wire_transfers (as)) { case WTS_SUCCESS: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "IBAN authorization finished!\n"); - return ANASTASIS_AUTHORIZATION_RES_FINISHED; + return ANASTASIS_AUTHORIZATION_SRES_FINISHED; case WTS_NOT_READY: break; /* continue below */ case WTS_FAILED_WITH_REPLY: - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED; case WTS_FAILED_WITHOUT_REPLY: - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED; } if (GNUNET_TIME_absolute_is_future (timeout)) { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending IBAN check until %s\n", + GNUNET_TIME_absolute2s (timeout)); as->connection = connection; MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_SRES_SUSPENDED; } - return respond_with_challenge (as, - connection); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Timeout reached at %s, failing request\n", + GNUNET_TIME_absolute2s (timeout)); + mres = TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_IBAN_MISSING_TRANSFER, + NULL); + if (MHD_YES != mres) + return ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "IBAN authorization finished!\n"); - return ANASTASIS_AUTHORIZATION_RES_FINISHED; + return ANASTASIS_AUTHORIZATION_SRES_FINISHED; } /* should be impossible */ GNUNET_break (0); - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED; } @@ -708,9 +719,9 @@ libanastasis_plugin_authorization_iban_init (void *cls) plugin->cls = ctx; plugin->validate = &iban_validate; plugin->start = &iban_start; - plugin->process = &iban_process; + plugin->challenge = &iban_challenge; + plugin->solve = &iban_solve; plugin->cleanup = &iban_cleanup; - return plugin; } diff --git a/src/authorization/anastasis_authorization_plugin_post.c b/src/authorization/anastasis_authorization_plugin_post.c index 4adeffd..9410b58 100644 --- a/src/authorization/anastasis_authorization_plugin_post.c +++ b/src/authorization/anastasis_authorization_plugin_post.c @@ -3,7 +3,7 @@ Copyright (C) 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -232,7 +232,7 @@ post_validate (void *cls, { if (MHD_NO == TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_POST_INVALID, "JSON malformed")) return GNUNET_SYSERR; @@ -248,7 +248,7 @@ post_validate (void *cls, json_decref (j); if (MHD_NO == TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_POST_INVALID, "JSON lacked required address information")) return GNUNET_SYSERR; @@ -334,8 +334,12 @@ post_done_cb (void *cls, { struct ANASTASIS_AUTHORIZATION_State *as = cls; - as->child = NULL; as->cwh = NULL; + if (NULL != as->child) + { + GNUNET_OS_process_destroy (as->child); + as->child = NULL; + } as->pst = type; as->exit_code = exit_code; MHD_resume_connection (as->connection); @@ -348,14 +352,12 @@ post_done_cb (void *cls, * I.e. start to send SMS or e-mail or launch video identification. * * @param as authorization state - * @param timeout how long do we have to produce a reply * @param connection HTTP client request (for queuing response, such as redirection to video portal) * @return state of the request */ -static enum ANASTASIS_AUTHORIZATION_Result -post_process (struct ANASTASIS_AUTHORIZATION_State *as, - struct GNUNET_TIME_Absolute timeout, - struct MHD_Connection *connection) +static enum ANASTASIS_AUTHORIZATION_ChallengeResult +post_challenge (struct ANASTASIS_AUTHORIZATION_State *as, + struct MHD_Connection *connection) { const char *mime; const char *lang; @@ -379,7 +381,6 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, GNUNET_JSON_spec_end () }; - (void) timeout; mime = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT); @@ -401,8 +402,8 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_POST_INVALID, "address information incomplete"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } if (NULL == as->msg) { @@ -418,8 +419,8 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED, "pipe"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, p, @@ -441,28 +442,19 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED, "exec"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } pipe_stdin = GNUNET_DISK_pipe_detach_end (p, GNUNET_DISK_PIPE_END_WRITE); GNUNET_assert (NULL != pipe_stdin); GNUNET_DISK_pipe_close (p); - { - char *tpk; - - tpk = GNUNET_STRINGS_data_to_string_alloc ( - &as->truth_uuid, - sizeof (as->truth_uuid)); - GNUNET_asprintf (&as->msg, - get_message (as->ctx->messages, - connection, - "body"), - (unsigned long long) as->code, - tpk); - GNUNET_free (tpk); - } - + GNUNET_asprintf (&as->msg, + get_message (as->ctx->messages, + connection, + "body"), + ANASTASIS_pin2s (as->code), + ANASTASIS_CRYPTO_uuid2s (&as->truth_uuid)); { const char *off = as->msg; size_t left = strlen (off); @@ -483,8 +475,8 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED, "write"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } as->msg_off += ret; off += ret; @@ -497,14 +489,14 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, as); as->connection = connection; MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; } if (NULL != as->cwh) { /* Spurious call, why are we here? */ GNUNET_break (0); MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; } if ( (GNUNET_OS_PROCESS_EXITED != as->pst) || (0 != as->exit_code) ) @@ -521,8 +513,8 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_POST_HELPER_COMMAND_FAILED, es); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } /* Build HTTP response */ @@ -533,12 +525,9 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, "application/json")) { resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint ( - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)), - GNUNET_JSON_pack_string ("detail", + GNUNET_JSON_pack_string ("challenge_type", + "TAN_SENT"), + GNUNET_JSON_pack_string ("tan_address_hint", zip)); } else @@ -558,12 +547,12 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_MHD_add_global_headers (resp); } mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_SUCCESS; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS; } } @@ -648,7 +637,7 @@ libanastasis_plugin_authorization_post_init (void *cls) plugin->cls = ctx; plugin->validate = &post_validate; plugin->start = &post_start; - plugin->process = &post_process; + plugin->challenge = &post_challenge; plugin->cleanup = &post_cleanup; if (GNUNET_OK != diff --git a/src/authorization/anastasis_authorization_plugin_sms.c b/src/authorization/anastasis_authorization_plugin_sms.c index 94b2c0d..695e5d8 100644 --- a/src/authorization/anastasis_authorization_plugin_sms.c +++ b/src/authorization/anastasis_authorization_plugin_sms.c @@ -3,7 +3,7 @@ Copyright (C) 2019, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -219,7 +219,7 @@ sms_validate (void *cls, { if (MHD_NO == TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_SMS_PHONE_INVALID, NULL)) return GNUNET_SYSERR; @@ -295,8 +295,12 @@ sms_done_cb (void *cls, { struct ANASTASIS_AUTHORIZATION_State *as = cls; - as->child = NULL; as->cwh = NULL; + if (NULL != as->child) + { + GNUNET_OS_process_destroy (as->child); + as->child = NULL; + } as->pst = type; as->exit_code = exit_code; MHD_resume_connection (as->connection); @@ -309,20 +313,17 @@ sms_done_cb (void *cls, * I.e. start to send SMS or e-mail or launch video identification. * * @param as authorization state - * @param timeout how long do we have to produce a reply * @param connection HTTP client request (for queuing response, such as redirection to video portal) * @return state of the request */ -static enum ANASTASIS_AUTHORIZATION_Result -sms_process (struct ANASTASIS_AUTHORIZATION_State *as, - struct GNUNET_TIME_Absolute timeout, - struct MHD_Connection *connection) +static enum ANASTASIS_AUTHORIZATION_ChallengeResult +sms_challenge (struct ANASTASIS_AUTHORIZATION_State *as, + struct MHD_Connection *connection) { MHD_RESULT mres; const char *mime; const char *lang; - (void) timeout; mime = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT); @@ -347,8 +348,8 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED, "pipe"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, p, @@ -366,26 +367,17 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED, "exec"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } pipe_stdin = GNUNET_DISK_pipe_detach_end (p, GNUNET_DISK_PIPE_END_WRITE); GNUNET_assert (NULL != pipe_stdin); GNUNET_DISK_pipe_close (p); - { - char *tpk; - - tpk = GNUNET_STRINGS_data_to_string_alloc ( - &as->truth_uuid, - sizeof (as->truth_uuid)); - GNUNET_asprintf (&as->msg, - "A-%llu\nAnastasis\n%s", - (unsigned long long) as->code, - tpk); - GNUNET_free (tpk); - } - + GNUNET_asprintf (&as->msg, + "%s\nAnastasis:\n%s", + ANASTASIS_pin2s (as->code), + ANASTASIS_CRYPTO_uuid2s (&as->truth_uuid)); { const char *off = as->msg; size_t left = strlen (off); @@ -406,8 +398,8 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED, "write"); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } as->msg_off += ret; off += ret; @@ -420,14 +412,14 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, as); as->connection = connection; MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; } if (NULL != as->cwh) { /* Spurious call, why are we here? */ GNUNET_break (0); MHD_suspend_connection (connection); - return ANASTASIS_AUTHORIZATION_RES_SUSPENDED; + return ANASTASIS_AUTHORIZATION_CRES_SUSPENDED; } if ( (GNUNET_OS_PROCESS_EXITED != as->pst) || (0 != as->exit_code) ) @@ -444,8 +436,8 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_EC_ANASTASIS_SMS_HELPER_COMMAND_FAILED, es); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_FAILED; } /* Build HTTP response */ @@ -464,12 +456,9 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, "application/json")) { resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint ( - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)), - GNUNET_JSON_pack_string ("detail", + GNUNET_JSON_pack_string ("challenge_type", + "TAN_SENT"), + GNUNET_JSON_pack_string ("tan_address_hint", end)); } else @@ -493,12 +482,12 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, "text/plain")); } mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) - return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED; - return ANASTASIS_AUTHORIZATION_RES_SUCCESS; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_CRES_SUCCESS; } } @@ -596,7 +585,7 @@ libanastasis_plugin_authorization_sms_init (void *cls) plugin->cls = ctx; plugin->validate = &sms_validate; plugin->start = &sms_start; - plugin->process = &sms_process; + plugin->challenge = &sms_challenge; plugin->cleanup = &sms_cleanup; if (GNUNET_OK != diff --git a/src/authorization/anastasis_authorization_plugin_totp.c b/src/authorization/anastasis_authorization_plugin_totp.c new file mode 100644 index 0000000..c127e38 --- /dev/null +++ b/src/authorization/anastasis_authorization_plugin_totp.c @@ -0,0 +1,381 @@ +/* + This totp is part of Anastasis + Copyright (C) 2021 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the totp COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @totp anastasis_authorization_plugin_totp.c + * @brief authorization plugin using totp + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis_authorization_plugin.h" +#include <taler/taler_mhd_lib.h> +#include <gnunet/gnunet_db_lib.h> +#include "anastasis_database_lib.h" +#include <gcrypt.h> + + +/** + * How many retries do we allow per code? + */ +#define INITIAL_RETRY_COUNTER 3 + +/** + * How long is a TOTP code valid? + */ +#define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * Range of time we allow (plus-minus). + */ +#define TIME_INTERVAL_RANGE 2 + +/** + * How long is the shared secret in bytes? + */ +#define SECRET_LEN 32 + + +/** + * Saves the state of a authorization process + */ +struct ANASTASIS_AUTHORIZATION_State +{ + /** + * UUID of the challenge which is authorised + */ + struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; + + /** + * Was the challenge satisfied? + */ + struct GNUNET_HashCode valid_replies[TIME_INTERVAL_RANGE * 2 + 1]; + + /** + * Our context. + */ + const struct ANASTASIS_AuthorizationContext *ac; + +}; + + +/** + * Validate @a data is a well-formed input into the challenge method, + * i.e. @a data is a well-formed phone number for sending an SMS, or + * a well-formed e-mail address for sending an e-mail. Not expected to + * check that the phone number or e-mail account actually exists. + * + * To be possibly used before issuing a 402 payment required to the client. + * + * @param cls closure with a `const struct ANASTASIS_AuthorizationContext *` + * @param connection HTTP client request (for queuing response) + * @param truth_mime mime type of @e data + * @param data input to validate (i.e. is it a valid phone number, etc.) + * @param data_length number of bytes in @a data + * @return #GNUNET_OK if @a data is valid, + * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection + * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection + */ +static enum GNUNET_GenericReturnValue +totp_validate (void *cls, + struct MHD_Connection *connection, + const char *truth_mime, + const char *data, + size_t data_length) +{ + (void) cls; + (void) truth_mime; + (void) connection; + if (NULL == data) + { + GNUNET_break_op (0); + if (MHD_NO == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_ANASTASIS_TOTP_KEY_MISSING, + NULL)) + return GNUNET_SYSERR; + return GNUNET_NO; + } + if (SECRET_LEN != data_length) + { + GNUNET_break_op (0); + if (MHD_NO == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_ANASTASIS_TOTP_KEY_INVALID, + NULL)) + return GNUNET_SYSERR; + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Compute TOTP code at current time with offset + * @a time_off for the @a key. + * + * @param time_off offset to apply when computing the code + * @param key input key material + * @param key_size number of bytes in @a key + * @return TOTP code at this time + */ +static uint64_t +compute_totp (int time_off, + const void *key, + size_t key_size) +{ + struct GNUNET_TIME_Absolute now; + time_t t; + uint64_t ctr; + uint8_t hmac[20]; /* SHA1: 20 bytes */ + + now = GNUNET_TIME_absolute_get (); + while (time_off < 0) + { + now = GNUNET_TIME_absolute_subtract (now, + TOTP_VALIDITY_PERIOD); + time_off++; + } + while (time_off > 0) + { + now = GNUNET_TIME_absolute_add (now, + TOTP_VALIDITY_PERIOD); + time_off--; + } + t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us; + ctr = GNUNET_htonll (t / 30LLU); + + { + gcry_md_hd_t md; + const unsigned char *mc; + + GNUNET_assert (GPG_ERR_NO_ERROR == + gcry_md_open (&md, + GCRY_MD_SHA1, + GCRY_MD_FLAG_HMAC)); + gcry_md_setkey (md, + key, + key_size); + gcry_md_write (md, + &ctr, + sizeof (ctr)); + mc = gcry_md_read (md, + GCRY_MD_SHA1); + GNUNET_assert (NULL != mc); + memcpy (hmac, + mc, + sizeof (hmac)); + gcry_md_close (md); + } + + { + uint32_t code = 0; + int offset; + + offset = hmac[sizeof (hmac) - 1] & 0x0f; + for (int count = 0; count < 4; count++) + code |= ((uint32_t) hmac[offset + 3 - count]) << (8 * count); + code &= 0x7fffffff; + /* always use 8 digits (maximum) */ + code = code % 100000000; + return code; + } +} + + +/** + * Begin issuing authentication challenge to user based on @a data. + * + * @param cls closure + * @param trigger function to call when we made progress + * @param trigger_cls closure for @a trigger + * @param truth_uuid Identifier of the challenge, to be (if possible) included in the + * interaction with the user + * @param code always 0 (direct validation, backend does + * not generate a code in this mode) + * @param data truth for input to validate (i.e. the shared secret) + * @param data_length number of bytes in @a data + * @return state to track progress on the authorization operation, NULL on failure + */ +static struct ANASTASIS_AUTHORIZATION_State * +totp_start (void *cls, + GNUNET_SCHEDULER_TaskCallback trigger, + void *trigger_cls, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + uint64_t code, + const void *data, + size_t data_length) +{ + const struct ANASTASIS_AuthorizationContext *ac = cls; + struct ANASTASIS_AUTHORIZATION_State *as; + uint64_t want; + unsigned int off = 0; + + GNUNET_assert (0 == code); + as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State); + as->ac = ac; + as->truth_uuid = *truth_uuid; + for (int i = -TIME_INTERVAL_RANGE; + i <= TIME_INTERVAL_RANGE; + i++) + { + want = compute_totp (i, + data, + data_length); + ANASTASIS_hash_answer (want, + &as->valid_replies[off++]); + } + return as; +} + + +/** + * Check authentication response from the user. + * + * @param as authorization state + * @param timeout how long do we have to produce a reply + * @param challenge_response hash of the response + * @param connection HTTP client request (for queuing response, such as redirection to video portal) + * @return state of the request + */ +static enum ANASTASIS_AUTHORIZATION_SolveResult +totp_solve (struct ANASTASIS_AUTHORIZATION_State *as, + struct GNUNET_TIME_Absolute timeout, + const struct GNUNET_HashCode *challenge_response, + struct MHD_Connection *connection) +{ + MHD_RESULT mres; + const char *mime; + const char *lang; + + for (unsigned int i = 0; i<=TIME_INTERVAL_RANGE * 2; i++) + if (0 == + GNUNET_memcmp (challenge_response, + &as->valid_replies[i])) + return ANASTASIS_AUTHORIZATION_SRES_FINISHED; + mime = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT); + if (NULL == mime) + mime = "text/plain"; + lang = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_LANGUAGE); + if (NULL == lang) + lang = "en"; + + /* Build HTTP response */ + { + struct MHD_Response *resp; + struct GNUNET_TIME_Timestamp now; + + now = GNUNET_TIME_timestamp_get (); + if (TALER_MHD_xmime_matches (mime, + "application/json")) + { + resp = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED)), + GNUNET_JSON_pack_timestamp ("server_time", + now)); + } + else + { + size_t response_size; + char *response; + + // FIXME: i18n of the message based on 'lang' ... + response_size + = GNUNET_asprintf (&response, + "Server time: %s", + GNUNET_TIME_timestamp2s (now)); + resp = MHD_create_response_from_buffer (response_size, + response, + MHD_RESPMEM_MUST_COPY); + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + } + mres = MHD_queue_response (connection, + MHD_HTTP_FORBIDDEN, + resp); + MHD_destroy_response (resp); + } + if (MHD_YES != mres) + return ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED; + return ANASTASIS_AUTHORIZATION_SRES_FAILED; +} + + +/** + * Free internal state associated with @a as. + * + * @param as state to clean up + */ +static void +totp_cleanup (struct ANASTASIS_AUTHORIZATION_State *as) +{ + GNUNET_free (as); +} + + +/** + * Initialize Totp based authorization plugin + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin` + */ +void * +libanastasis_plugin_authorization_totp_init (void *cls) +{ + const struct ANASTASIS_AuthorizationContext *ac = cls; + struct ANASTASIS_AuthorizationPlugin *plugin; + + plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin); + plugin->cls = (void *) ac; + plugin->user_provided_code = true; + plugin->retry_counter = INITIAL_RETRY_COUNTER; + plugin->code_validity_period = TOTP_VALIDITY_PERIOD; + plugin->code_rotation_period = plugin->code_validity_period; + plugin->code_retransmission_frequency = plugin->code_validity_period; + plugin->validate = &totp_validate; + plugin->start = &totp_start; + plugin->solve = &totp_solve; + plugin->cleanup = &totp_cleanup; + return plugin; +} + + +/** + * Unload authorization plugin + * + * @param cls a `struct ANASTASIS_AuthorizationPlugin` + * @return NULL (always) + */ +void * +libanastasis_plugin_authorization_totp_done (void *cls) +{ + struct ANASTASIS_AuthorizationPlugin *plugin = cls; + + GNUNET_free (plugin); + return NULL; +} diff --git a/src/authorization/authorization-email-messages.json b/src/authorization/authorization-email-messages.json index 5a2e048..5e4ddb3 100644 --- a/src/authorization/authorization-email-messages.json +++ b/src/authorization/authorization-email-messages.json @@ -3,8 +3,8 @@ "instructions_i18n" : { "de_DE" : "Ein Authorisierungscode wurde an %.*s@DOMAIN geschickt" }, - "body" : "Your Anastasis recovery code is:\nA-%llu\n\nThis is for challenge %s.\n", + "body" : "Your Anastasis recovery code is:\n%s\n\nThis is for challenge %s.\n", "body_i18n" : { - "de_DE" : "Ihr Anastasis Autorisierungscode ist:\nA-%llu\n\nDies ist der Code für den Vorgang %s.\n" + "de_DE" : "Ihr Anastasis Autorisierungscode ist:\n%s\n\nDies ist der Code für den Vorgang %s.\n" } } diff --git a/src/authorization/authorization-post-messages.json b/src/authorization/authorization-post-messages.json index d2ac83a..c48c8ab 100644 --- a/src/authorization/authorization-post-messages.json +++ b/src/authorization/authorization-post-messages.json @@ -3,5 +3,5 @@ "instructions_i18n" : { "de_DE" : "Ein Authorisierungscode wurde an eine Addresse mit der Postleitzahl %s geschickt" }, - "body" : "Dear Customer\n\nThe Anastasis recovery code you need to\nrecover your data is A-%llu.\nThis is for challenge %s.\n\nBest regards\n\nYour Anastasis provider" + "body" : "Dear Customer\n\nThe Anastasis recovery code you need to\nrecover your data is %s.\nThis is for challenge %s.\n\nBest regards\n\nYour Anastasis provider" } diff --git a/src/authorization/iban.h b/src/authorization/iban.h index 70db7ea..17836f4 100644 --- a/src/authorization/iban.h +++ b/src/authorization/iban.h @@ -3,7 +3,7 @@ Copyright (C) 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY diff --git a/src/authorization/libanastasiseufin/lae_common.h b/src/authorization/libanastasiseufin/lae_common.h index 8879ba3..1c2c8fd 100644 --- a/src/authorization/libanastasiseufin/lae_common.h +++ b/src/authorization/libanastasiseufin/lae_common.h @@ -36,7 +36,7 @@ * @param auth authentication data to use * @return #GNUNET_OK in success */ -int +enum GNUNET_GenericReturnValue ANASTASIS_EUFIN_setup_auth_ ( CURL *easy, const struct ANASTASIS_EUFIN_AuthenticationData *auth); diff --git a/src/authorization/libanastasiseufin/lae_credit.c b/src/authorization/libanastasiseufin/lae_credit.c index e8cabb5..dd6687b 100644 --- a/src/authorization/libanastasiseufin/lae_credit.c +++ b/src/authorization/libanastasiseufin/lae_credit.c @@ -65,7 +65,7 @@ struct ANASTASIS_EUFIN_CreditHistoryHandle * were set, * #GNUNET_SYSERR if there was a protocol violation in @a history */ -static int +static enum GNUNET_GenericReturnValue parse_account_history (struct ANASTASIS_EUFIN_CreditHistoryHandle *hh, const json_t *history) { @@ -82,15 +82,15 @@ parse_account_history (struct ANASTASIS_EUFIN_CreditHistoryHandle *hh, GNUNET_break_op (0); return GNUNET_SYSERR; } - for (unsigned int i = 0; i<json_array_size (history_array); i++) + for (size_t i = 0; i<json_array_size (history_array); i++) { struct ANASTASIS_EUFIN_CreditDetails td; uint64_t row_id; struct GNUNET_JSON_Specification hist_spec[] = { TALER_JSON_spec_amount_any ("amount", &td.amount), - TALER_JSON_spec_absolute_time ("date", - &td.execution_date), + GNUNET_JSON_spec_timestamp ("date", + &td.execution_date), GNUNET_JSON_spec_uint64 ("row_id", &row_id), GNUNET_JSON_spec_string ("subject", diff --git a/src/authorization/test-post.sh b/src/authorization/test-post.sh new file mode 100755 index 0000000..fd3a8d8 --- /dev/null +++ b/src/authorization/test-post.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# This file is in the public domain. +set -eu +ADDR=`jq -n '{ + full_name: "John Doe", + street: "Bar street 3", + city: "Wuppertal", + postcode: 42289, + country: "DE", + }'` + +echo "Your recovery code is 1234" | ./anastasis-authorization-post.sh "$ADDR" diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 1046810..c1b0931 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -18,11 +18,14 @@ anastasis_httpd_SOURCES = \ anastasis-httpd.c anastasis-httpd.h \ anastasis-httpd_mhd.c anastasis-httpd_mhd.h \ anastasis-httpd_policy.c anastasis-httpd_policy.h \ - anastasis-httpd_policy_upload.c \ - anastasis-httpd_truth.c anastasis-httpd_truth.h \ + anastasis-httpd_policy-meta.c anastasis-httpd_policy-meta.h \ + anastasis-httpd_policy-upload.c \ + anastasis-httpd_truth.h \ anastasis-httpd_terms.c anastasis-httpd_terms.h \ anastasis-httpd_config.c anastasis-httpd_config.h \ - anastasis-httpd_truth_upload.c + anastasis-httpd_truth-challenge.c \ + anastasis-httpd_truth-solve.c \ + anastasis-httpd_truth-upload.c anastasis_httpd_LDADD = \ $(top_builddir)/src/util/libanastasisutil.la \ @@ -34,11 +37,9 @@ anastasis_httpd_LDADD = \ -ltalerjson \ -ltalerutil \ -lgnunetcurl \ - -lgnunetrest \ -lgnunetjson \ -lgnunetutil \ -lmicrohttpd \ - -luuid \ $(XLIB) EXTRA_DIST = \ diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c index 9f5c87b..ebfb0ae 100644 --- a/src/backend/anastasis-httpd.c +++ b/src/backend/anastasis-httpd.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - (C) 2020 Anastasis SARL + (C) 2020-2022 Anastasis SARL Anastasis 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 @@ -26,17 +26,13 @@ #include "anastasis-httpd_mhd.h" #include "anastasis_database_lib.h" #include "anastasis-httpd_policy.h" +#include "anastasis-httpd_policy-meta.h" #include "anastasis-httpd_truth.h" #include "anastasis-httpd_terms.h" #include "anastasis-httpd_config.h" /** - * Backlog for listen operation on unix-domain sockets. - */ -#define UNIX_BACKLOG 500 - -/** * Upload limit to the service, in megabytes. */ unsigned long long int AH_upload_limit_mb; @@ -72,11 +68,6 @@ const struct GNUNET_CONFIGURATION_Handle *AH_cfg; char *AH_backend_url; /** - * Taler currency. - */ -char *AH_currency; - -/** * Our fulfillment URL. */ char *AH_fulfillment_url; @@ -87,9 +78,9 @@ char *AH_fulfillment_url; char *AH_business_name; /** - * Our server salt. + * Our provider salt. */ -struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt; +struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt; /** * Number of policy uploads permitted per annual fee payment. @@ -295,7 +286,7 @@ url_handler (void *cls, &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND }, { "/terms", MHD_HTTP_METHOD_GET, NULL, NULL, 0, - &AH_handler_terms, MHD_HTTP_OK }, + &AH_handler_privacy, MHD_HTTP_OK }, { "/privacy", MHD_HTTP_METHOD_GET, NULL, NULL, 0, &AH_handler_terms, MHD_HTTP_OK }, @@ -360,12 +351,15 @@ url_handler (void *cls, strlen ("/policy/"))) { const char *account = url + strlen ("/policy/"); + const char *end = strchr (account, '/'); struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; if (GNUNET_OK != GNUNET_STRINGS_string_to_data ( account, - strlen (account), + (NULL == end) + ? strlen (account) + : end - account, &account_pub, sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP))) { @@ -374,14 +368,23 @@ url_handler (void *cls, TALER_EC_GENERIC_PARAMETER_MALFORMED, "account public key"); } + if ( (NULL != end) && + (0 != strcmp (end, + "/meta")) ) + return TMH_MHD_handler_static_response (&h404, + connection); if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) { - return AH_policy_get (connection, - &account_pub); + if (NULL == end) + return AH_policy_get (connection, + &account_pub); + return AH_policy_meta_get (connection, + &account_pub); } - if (0 == strcmp (method, - MHD_HTTP_METHOD_POST)) + if ( (0 == strcmp (method, + MHD_HTTP_METHOD_POST)) && + (NULL == end) ) { return AH_handler_policy_post (connection, hc, @@ -389,6 +392,11 @@ url_handler (void *cls, upload_data, upload_data_size); } + if (0 == strcmp (method, + MHD_HTTP_METHOD_OPTIONS)) + { + return TALER_MHD_reply_cors_preflight (connection); + } return TMH_MHD_handler_static_response (&h405, connection); } @@ -398,12 +406,20 @@ url_handler (void *cls, { struct ANASTASIS_CRYPTO_TruthUUIDP tu; const char *pub_key_str; + const char *end; + size_t len; pub_key_str = &url[strlen ("/truth/")]; + end = strchr (pub_key_str, + '/'); + if (NULL == end) + len = strlen (pub_key_str); + else + len = end - pub_key_str; if (GNUNET_OK != GNUNET_STRINGS_string_to_data ( pub_key_str, - strlen (pub_key_str), + len, &tu, sizeof(tu))) { @@ -413,15 +429,19 @@ url_handler (void *cls, TALER_EC_GENERIC_PARAMETER_MALFORMED, "truth UUID"); } + if ( (NULL != end) && + (0 != strcmp (end, "/solve")) && + (0 != strcmp (end, "/challenge")) ) + return TMH_MHD_handler_static_response (&h404, + connection); if (0 == strcmp (method, - MHD_HTTP_METHOD_GET)) - { - return AH_handler_truth_get (connection, - &tu, - hc); - } - if (0 == strcmp (method, + MHD_HTTP_METHOD_OPTIONS)) + return TALER_MHD_reply_cors_preflight (connection); + if (0 != strcmp (method, MHD_HTTP_METHOD_POST)) + return TMH_MHD_handler_static_response (&h405, + connection); + if (NULL == end) { return AH_handler_truth_post (connection, hc, @@ -429,9 +449,27 @@ url_handler (void *cls, upload_data, upload_data_size); } - return TMH_MHD_handler_static_response (&h405, - connection); - } + if (0 == strcmp (end, + "/solve")) + { + return AH_handler_truth_solve (connection, + hc, + &tu, + upload_data, + upload_data_size); + } + if (0 == strcmp (end, + "/challenge")) + { + return AH_handler_truth_challenge (connection, + hc, + &tu, + upload_data, + upload_data_size); + } + /* should be impossible to get here */ + GNUNET_assert (0); + } /* end of "/truth/" prefix */ path_matched = false; for (unsigned int i = 0; NULL != handlers[i].url; i++) { @@ -474,7 +512,8 @@ do_shutdown (void *cls) { (void) cls; AH_resume_all_bc (); - AH_truth_shutdown (); + AH_truth_challenge_shutdown (); + AH_truth_solve_shutdown (); AH_truth_upload_shutdown (); if (NULL != mhd_task) { @@ -712,34 +751,6 @@ run (void *cls, return; } if (GNUNET_OK != - TALER_config_get_currency (config, - &AH_currency)) - { - GNUNET_SCHEDULER_shutdown (); - return; - } - if (0 != strcasecmp (AH_currency, - AH_annual_fee.currency)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "anastasis", - "ANNUAL_FEE", - "currency mismatch"); - GNUNET_SCHEDULER_shutdown (); - return; - } - if (GNUNET_OK != - TALER_amount_cmp_currency (&AH_insurance, - &AH_annual_fee)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "anastasis", - "INSURANCE", - "currency mismatch"); - GNUNET_SCHEDULER_shutdown (); - return; - } - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (config, "anastasis-merchant-backend", "PAYMENT_BACKEND_URL", @@ -817,30 +828,30 @@ run (void *cls, return; } { - char *server_salt; + char *provider_salt; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (config, "anastasis", - "SERVER_SALT", - &server_salt)) + "PROVIDER_SALT", + &provider_salt)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "anastasis", - "SERVER_SALT"); + "PROVIDER_SALT"); GNUNET_SCHEDULER_shutdown (); return; } GNUNET_assert (GNUNET_YES == - GNUNET_CRYPTO_kdf (&AH_server_salt, - sizeof (AH_server_salt), - "anastasis-server-salt", - strlen ("anastasis-server-salt"), - server_salt, - strlen (server_salt), + GNUNET_CRYPTO_kdf (&AH_provider_salt, + sizeof (AH_provider_salt), + "anastasis-provider-salt", + strlen ("anastasis-provider-salt"), + provider_salt, + strlen (provider_salt), NULL, 0)); - GNUNET_free (server_salt); + GNUNET_free (provider_salt); } /* setup HTTP client event loop */ @@ -971,7 +982,6 @@ main (int argc, "CERTTYPE", "type of the TLS client certificate, defaults to PEM if not specified", &certtype), - GNUNET_GETOPT_OPTION_END }; diff --git a/src/backend/anastasis-httpd.h b/src/backend/anastasis-httpd.h index 33e0504..1a87921 100644 --- a/src/backend/anastasis-httpd.h +++ b/src/backend/anastasis-httpd.h @@ -175,11 +175,6 @@ extern struct TALER_Amount AH_question_cost; extern char *AH_backend_url; /** - * Taler currency. - */ -extern char *AH_currency; - -/** * Heap for processing timeouts of requests. */ extern struct GNUNET_CONTAINER_Heap *AH_to_heap; @@ -205,9 +200,9 @@ extern char *AH_fulfillment_url; extern char *AH_business_name; /** - * Our server salt. + * Our provider salt. */ -extern struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt; +extern struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt; /** * Our context for making HTTP requests. diff --git a/src/backend/anastasis-httpd_config.c b/src/backend/anastasis-httpd_config.c index d96d57e..315419e 100644 --- a/src/backend/anastasis-httpd_config.c +++ b/src/backend/anastasis-httpd_config.c @@ -1,9 +1,9 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020, 2021, 2024 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -78,48 +78,83 @@ MHD_RESULT AH_handler_config (struct AH_RequestHandler *rh, struct MHD_Connection *connection) { - json_t *method_arr = json_array (); + static struct MHD_Response *response; + static struct GNUNET_TIME_Absolute a; - GNUNET_assert (NULL != method_arr); + if ( (GNUNET_TIME_absolute_is_past (a)) && + (NULL != response) ) { - json_t *method; + MHD_destroy_response (response); + response = NULL; + } + if (NULL == response) + { + json_t *method_arr = json_array (); + struct GNUNET_TIME_Timestamp km; + char dat[128]; + + GNUNET_assert (NULL != method_arr); + a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + GNUNET_TIME_UNIT_DAYS); + a = GNUNET_TIME_absolute_add (a, + GNUNET_TIME_UNIT_DAYS); + /* => /config response stays at most 48h in caches! */ + km = GNUNET_TIME_absolute_to_timestamp (a); + TALER_MHD_get_date_string (km.abs_time, + dat); + { + json_t *method; + + method = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "question"), + TALER_JSON_pack_amount ("cost", + &AH_question_cost)); + GNUNET_assert ( + 0 == + json_array_append_new (method_arr, + method)); + } + GNUNET_CONFIGURATION_iterate_sections (AH_cfg, + &add_methods, + method_arr); - method = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "question"), - TALER_JSON_pack_amount ("cost", - &AH_question_cost)); - GNUNET_assert ( - 0 == - json_array_append_new (method_arr, - method)); + response = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_string ("name", + "anastasis"), + GNUNET_JSON_pack_string ("version", + "0:2:0"), + GNUNET_JSON_pack_string ("implementation", + "urn:net:taler:specs:anastasis:c-reference"), + GNUNET_JSON_pack_string ("business_name", + AH_business_name), + GNUNET_JSON_pack_array_steal ("methods", + method_arr), + GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes", + AH_upload_limit_mb), + TALER_JSON_pack_amount ("annual_fee", + &AH_annual_fee), + TALER_JSON_pack_amount ("truth_upload_fee", + &AH_truth_upload_fee), + TALER_JSON_pack_amount ("liability_limit", + &AH_insurance), + GNUNET_JSON_pack_data_auto ("provider_salt", + &AH_provider_salt)); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_EXPIRES, + dat)); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,max-age=21600")); /* 6h */ } - GNUNET_CONFIGURATION_iterate_sections (AH_cfg, - &add_methods, - method_arr); - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("name", - "anastasis"), - GNUNET_JSON_pack_string ("version", - "0:0:0"), - GNUNET_JSON_pack_string ("business_name", - AH_business_name), - GNUNET_JSON_pack_string ("currency", - (char *) AH_currency), - GNUNET_JSON_pack_array_steal ("methods", - method_arr), - GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes", - AH_upload_limit_mb), - TALER_JSON_pack_amount ("annual_fee", - &AH_annual_fee), - TALER_JSON_pack_amount ("truth_upload_fee", - &AH_truth_upload_fee), - TALER_JSON_pack_amount ("liability_limit", - &AH_insurance), - GNUNET_JSON_pack_data_auto ("server_salt", - &AH_server_salt)); + return MHD_queue_response (connection, + MHD_HTTP_OK, + response); } diff --git a/src/backend/anastasis-httpd_policy-meta.c b/src/backend/anastasis-httpd_policy-meta.c new file mode 100644 index 0000000..67acc52 --- /dev/null +++ b/src/backend/anastasis-httpd_policy-meta.c @@ -0,0 +1,192 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file anastasis-httpd_policy-meta.c + * @brief functions to handle incoming requests on /policy/$PID/meta + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis-httpd.h" +#include "anastasis-httpd_policy-meta.h" +#include "anastasis_service.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_rest_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_merchant_service.h> +#include <taler/taler_signatures.h> + + +/** + * Function called on matching meta data. Note that if the client did + * not provide meta data for @a version, the function will be called + * with @a recovery_meta_data being NULL. + * + * @param cls closure with a `json_t *` to build up + * @param version the version of the recovery document + * @param ts timestamp when the document was created + * @param recovery_meta_data contains meta data about the encrypted recovery document + * @param recovery_meta_data_size size of @a recovery_meta_data blob + * @return #GNUNET_OK to continue to iterate, #GNUNET_NO to abort iteration + */ +static enum GNUNET_GenericReturnValue +build_meta_result (void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp ts, + const void *recovery_meta_data, + size_t recovery_meta_data_size) +{ + json_t *result = cls; + char version_s[14]; + + GNUNET_snprintf (version_s, + sizeof (version_s), + "%u", + (unsigned int) version); + GNUNET_assert (0 == + json_object_set_new ( + result, + version_s, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_varsize ( + "meta", + recovery_meta_data, + recovery_meta_data_size), + GNUNET_JSON_pack_timestamp ( + "upload_time", + ts)))); + return GNUNET_OK; +} + + +/** + * Return the meta data on recovery documents of @a account on @a + * connection. + * + * @param connection MHD connection to use + * @param account_pub account to query + * @return MHD result code + */ +static MHD_RESULT +return_policy_meta ( + struct MHD_Connection *connection, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub) +{ + enum GNUNET_DB_QueryStatus qs; + uint32_t max_version = INT32_MAX; /* Postgres is using signed ints... */ + json_t *result; + + { + const char *version_s; + + version_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "max_version"); + if (NULL != version_s) + { + char dummy; + + if (1 != sscanf (version_s, + "%u%c", + &max_version, + &dummy)) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "version"); + } + if (max_version > INT32_MAX) + max_version = INT32_MAX; /* cap to signed range */ + } + } + result = json_object (); + GNUNET_assert (NULL != result); + qs = db->get_recovery_meta_data (db->cls, + account_pub, + max_version, + &build_meta_result, + result); + + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_recovery_document"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "get_recovery_document"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_ANASTASIS_POLICY_NOT_FOUND, + NULL); + default: + /* interesting case below */ + break; + } + + return TALER_MHD_reply_json_steal (connection, + result, + MHD_HTTP_OK); +} + + +MHD_RESULT +AH_policy_meta_get ( + struct MHD_Connection *connection, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub) +{ + struct GNUNET_HashCode recovery_data_hash; + enum ANASTASIS_DB_AccountStatus as; + uint32_t version; + struct GNUNET_TIME_Timestamp expiration; + + as = db->lookup_account (db->cls, + account_pub, + &expiration, + &recovery_data_hash, + &version); + switch (as) + { + case ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_ANASTASIS_POLICY_NOT_FOUND, + "account expired: payment required"); + case ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup account"); + case ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_ANASTASIS_POLICY_NOT_FOUND, + "no such account"); + case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED: + /* We have results, should fetch and return them! */ + break; + } + return return_policy_meta (connection, + account_pub); +} diff --git a/src/backend/anastasis-httpd_policy-meta.h b/src/backend/anastasis-httpd_policy-meta.h new file mode 100644 index 0000000..8c48fc6 --- /dev/null +++ b/src/backend/anastasis-httpd_policy-meta.h @@ -0,0 +1,41 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file anastasis-httpd_policy-meta.h + * @brief functions to handle incoming requests on /policy/ + * @author Dennis Neufeld + * @author Dominik Meister + * @author Christian Grothoff + */ +#ifndef ANASTASIS_HTTPD_POLICY_META_H +#define ANASTASIS_HTTPD_POLICY_META_H +#include <microhttpd.h> + + +/** + * Handle GET /policy/$ACCOUNT_PUB/meta request. + * + * @param connection the MHD connection to handle + * @param account_pub public key of the account + * @return MHD result code + */ +MHD_RESULT +AH_policy_meta_get ( + struct MHD_Connection *connection, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub); + + +#endif diff --git a/src/backend/anastasis-httpd_policy_upload.c b/src/backend/anastasis-httpd_policy-upload.c index c36cc17..83e8117 100644 --- a/src/backend/anastasis-httpd_policy_upload.c +++ b/src/backend/anastasis-httpd_policy-upload.c @@ -35,7 +35,7 @@ * we are awaiting payment before giving up? */ #define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MINUTES, 30) + GNUNET_TIME_UNIT_SECONDS, 30) /** @@ -86,6 +86,16 @@ struct PolicyUploadContext char *upload; /** + * Meta data uploaded by the client, or NULL for none. + */ + void *meta_data; + + /** + * Number of bytes in @e meta_data. + */ + size_t meta_data_size; + + /** * Used while we are awaiting proposal creation. */ struct TALER_MERCHANT_PostOrdersHandle *po; @@ -114,7 +124,7 @@ struct PolicyUploadContext * Timestamp of the order in @e payment_identifier. Used to * select the most recent unpaid offer. */ - struct GNUNET_TIME_Absolute existing_pi_timestamp; + struct GNUNET_TIME_Timestamp existing_pi_timestamp; /** * When does the operation timeout? @@ -125,13 +135,13 @@ struct PolicyUploadContext * How long must the account be valid? Determines whether we should * trigger payment, and if so how much. */ - struct GNUNET_TIME_Absolute end_date; + struct GNUNET_TIME_Timestamp end_date; /** * How long is the account already valid? * Determines how much the user needs to pay. */ - struct GNUNET_TIME_Absolute paid_until; + struct GNUNET_TIME_Timestamp paid_until; /** * Expected total upload size. @@ -220,6 +230,7 @@ cleanup_ctx (struct TM_HandlerContext *hc) if (NULL != puc->resp) MHD_destroy_response (puc->resp); GNUNET_free (puc->upload); + GNUNET_free (puc->meta_data); GNUNET_free (puc); } @@ -323,11 +334,10 @@ proposal_cb (void *cls, AH_trigger_daemon (NULL); if (MHD_HTTP_OK != por->hr.http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Backend returned status %u/%d\n", + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend returned status %u/%d when trying to setup order\n", por->hr.http_status, (int) por->hr.ec); - GNUNET_break (0); puc->resp = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_uint64 ("code", TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR), @@ -340,7 +350,7 @@ proposal_cb (void *cls, GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("backend-reply", (json_t *) por->hr.reply))); - puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + puc->response_code = MHD_HTTP_BAD_GATEWAY; return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -377,15 +387,14 @@ proposal_cb (void *cls, * Callback to process a GET /check-payment request * * @param cls our `struct PolicyUploadContext` - * @param hr HTTP response details * @param osr order status */ static void check_payment_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, const struct TALER_MERCHANT_OrderStatusResponse *osr) { struct PolicyUploadContext *puc = cls; + const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; /* refunds are not supported, verify */ puc->cpo = NULL; @@ -412,10 +421,12 @@ check_payment_cb (void *cls, puc->response_code = MHD_HTTP_BAD_GATEWAY; return; } + + GNUNET_assert (MHD_HTTP_OK == hr->http_status); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Payment status checked: %s\n", - osr->status ? "paid" : "unpaid"); - switch (osr->status) + "Payment status checked: %d\n", + osr->details.ok.status); + switch (osr->details.ok.status) { case TALER_MERCHANT_OSC_PAID: { @@ -425,13 +436,12 @@ check_payment_cb (void *cls, const json_t *contract; struct TALER_Amount amount; struct GNUNET_JSON_Specification cspec[] = { - TALER_JSON_spec_amount ("amount", - AH_currency, - &amount), + TALER_JSON_spec_amount_any ("amount", + &amount), GNUNET_JSON_spec_end () }; - contract = osr->details.paid.contract_terms; + contract = osr->details.ok.details.paid.contract_terms; if (GNUNET_OK != GNUNET_JSON_parse (contract, cspec, @@ -476,7 +486,7 @@ check_payment_cb (void *cls, case TALER_MERCHANT_OSC_CLAIMED: break; } - if (0 != puc->existing_pi_timestamp.abs_value_us) + if (! GNUNET_TIME_absolute_is_zero (puc->existing_pi_timestamp.abs_time)) { /* repeat payment request */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -527,7 +537,6 @@ await_payment (struct PolicyUploadContext *puc) AH_backend_url, order_id, NULL /* our payments are NOT session-bound */, - false, timeout, &check_payment_cb, puc); @@ -548,6 +557,7 @@ await_payment (struct PolicyUploadContext *puc) static MHD_RESULT begin_payment (struct PolicyUploadContext *puc) { + static const char *no_uuids[1] = { NULL }; json_t *order; GNUNET_CONTAINER_DLL_insert (puc_head, @@ -575,6 +585,10 @@ begin_payment (struct PolicyUploadContext *puc) order_id = GNUNET_STRINGS_data_to_string_alloc ( &puc->payment_identifier, sizeof(struct ANASTASIS_PaymentSecretP)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Creating order for %u years with payment of %s\n", + puc->years_to_pay, + TALER_amount2s (&upload_fee)); order = json_pack ("{s:o, s:s, s:[{s:s,s:I,s:s}], s:s }", "amount", TALER_JSON_from_amount (&upload_fee), "summary", "Anastasis policy storage fee", @@ -594,7 +608,7 @@ begin_payment (struct PolicyUploadContext *puc) 0, NULL, /* no inventory products */ 0, - NULL, /* no uuids */ + no_uuids, /* no uuids */ false, /* do NOT require claim token */ &proposal_cb, puc); @@ -648,35 +662,44 @@ AH_handler_policy_post ( hc->cc = &cleanup_ctx; puc->con = connection; + TALER_MHD_parse_request_header_auto (connection, + ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER, + &puc->payment_identifier, + puc->payment_identifier_provided); + puc->account = *account_pub; + + /* check for meta-data */ { - const char *pay_id; + const char *metas; - pay_id = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - if (NULL != pay_id) + metas = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + ANASTASIS_HTTP_HEADER_POLICY_META_DATA); + if (NULL == metas) { - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - pay_id, - strlen (pay_id), - &puc->payment_identifier, - sizeof (struct ANASTASIS_PaymentSecretP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER - " header must be a base32-encoded Payment-Secret"); - } - puc->payment_identifier_provided = true; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Policy upload started with payment identifier `%s'\n", - pay_id); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, + ANASTASIS_HTTP_HEADER_POLICY_META_DATA + " header must be present"); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data_alloc (metas, + strlen (metas), + &puc->meta_data, + &puc->meta_data_size)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, + ANASTASIS_HTTP_HEADER_POLICY_META_DATA + " header must include a base32-encoded value"); } } - puc->account = *account_pub; /* now setup 'puc' */ { const char *lens; @@ -721,28 +744,10 @@ AH_handler_policy_post ( } puc->upload_size = (size_t) len; } - { - /* Check if header contains Anastasis-Policy-Signature */ - const char *sig_s; - sig_s = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE); - if ( (NULL == sig_s) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data (sig_s, - strlen (sig_s), - &puc->account_sig, - sizeof (puc->account_sig))) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_ANASTASIS_POLICY_BAD_SIGNATURE, - ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE - " header must include a base32-encoded EdDSA signature"); - } - } + TALER_MHD_parse_request_header_auto_t (connection, + ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE, + &puc->account_sig); { /* Check if header contains an ETAG */ const char *etag; @@ -751,9 +756,12 @@ AH_handler_policy_post ( MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH); if ( (NULL == etag) || + (2 >= strlen (etag)) || + ('"' != etag[0]) || + ('"' != etag[strlen (etag) - 1]) || (GNUNET_OK != - GNUNET_STRINGS_string_to_data (etag, - strlen (etag), + GNUNET_STRINGS_string_to_data (etag + 1, + strlen (etag) - 2, &puc->new_policy_upload_hash, sizeof (puc->new_policy_upload_hash))) ) { @@ -787,39 +795,10 @@ AH_handler_policy_post ( } } - { - const char *long_poll_timeout_ms; - - long_poll_timeout_ms = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != long_poll_timeout_ms) - { - unsigned int timeout; - char dummy; - - if (1 != sscanf (long_poll_timeout_ms, - "%u%c", - &timeout, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms (must be non-negative number)"); - } - puc->timeout - = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( - GNUNET_TIME_UNIT_MILLISECONDS, - timeout)); - } - else - { - puc->timeout = GNUNET_TIME_relative_to_absolute - (CHECK_PAYMENT_GENERIC_TIMEOUT); - } - } + puc->timeout = GNUNET_TIME_relative_to_absolute ( + CHECK_PAYMENT_GENERIC_TIMEOUT); + TALER_MHD_parse_request_timeout (connection, + &puc->timeout); /* check if the client insists on paying */ { @@ -847,9 +826,9 @@ AH_handler_policy_post ( } else { - years = 0; + years = 1; } - puc->end_date = GNUNET_TIME_relative_to_absolute ( + puc->end_date = GNUNET_TIME_relative_to_timestamp ( GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, years)); } @@ -861,11 +840,15 @@ AH_handler_policy_post ( { struct GNUNET_TIME_Relative rem; - rem = GNUNET_TIME_absolute_get_remaining (puc->end_date); + rem = GNUNET_TIME_absolute_get_remaining (puc->end_date.abs_time); puc->years_to_pay = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us; if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)) puc->years_to_pay++; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Calculated years to pay to be %u until %s\n", + puc->years_to_pay, + GNUNET_TIME_absolute2s (puc->end_date.abs_time)); if (puc->payment_identifier_provided) { @@ -906,18 +889,14 @@ AH_handler_policy_post ( if (! puc->payment_identifier_provided) { - struct TALER_Amount zero_amount; enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Relative rel; - TALER_amount_set_zero (AH_currency, - &zero_amount); /* generate fresh payment identifier */ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, &puc->payment_identifier, sizeof (struct ANASTASIS_PaymentSecretP)); - if (0 != TALER_amount_cmp (&AH_annual_fee, - &zero_amount)) + if (! TALER_amount_is_zero (&AH_annual_fee)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No payment identifier, requesting payment\n"); @@ -941,10 +920,10 @@ AH_handler_policy_post ( ANASTASIS_MAX_YEARS_STORAGE); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Policy lifetime is %s (%u years)\n", - GNUNET_STRINGS_relative_time_to_string (rel, - GNUNET_YES), + GNUNET_TIME_relative2s (rel, + true), ANASTASIS_MAX_YEARS_STORAGE); - puc->paid_until = GNUNET_TIME_relative_to_absolute (rel); + puc->paid_until = GNUNET_TIME_relative_to_timestamp (rel); qs = db->update_lifetime (db->cls, account_pub, &puc->payment_identifier, @@ -965,7 +944,7 @@ AH_handler_policy_post ( struct GNUNET_HashCode hc; enum ANASTASIS_DB_AccountStatus as; uint32_t version; - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp now; struct GNUNET_TIME_Relative rem; as = db->lookup_account (db->cls, @@ -973,16 +952,21 @@ AH_handler_policy_post ( &puc->paid_until, &hc, &version); - now = GNUNET_TIME_absolute_get (); - if (puc->paid_until.abs_value_us < now.abs_value_us) + now = GNUNET_TIME_timestamp_get (); + if (GNUNET_TIME_timestamp_cmp (puc->paid_until, + <, + now)) puc->paid_until = now; - rem = GNUNET_TIME_absolute_get_difference (puc->paid_until, - puc->end_date); + rem = GNUNET_TIME_absolute_get_difference (puc->paid_until.abs_time, + puc->end_date.abs_time); puc->years_to_pay = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us; if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)) puc->years_to_pay++; - + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Calculated years to pay to be %u until %s\n", + puc->years_to_pay, + GNUNET_TIME_absolute2s (puc->end_date.abs_time)); if ( (ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED == as) && (0 != puc->years_to_pay) ) { @@ -1113,6 +1097,8 @@ AH_handler_policy_post ( &puc->new_policy_upload_hash, puc->upload, puc->upload_size, + puc->meta_data, + puc->meta_data_size, &puc->payment_identifier, &version); GNUNET_snprintf (version_s, @@ -1123,7 +1109,7 @@ AH_handler_policy_post ( sizeof (expir_s), "%llu", (unsigned long long) - (puc->paid_until.abs_value_us + (puc->paid_until.abs_time.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); switch (ss) { diff --git a/src/backend/anastasis-httpd_policy.c b/src/backend/anastasis-httpd_policy.c index 165be44..177cc06 100644 --- a/src/backend/anastasis-httpd_policy.c +++ b/src/backend/anastasis-httpd_policy.c @@ -30,17 +30,9 @@ #include <taler/taler_merchant_service.h> #include <taler/taler_signatures.h> -/** - * How long do we hold an HTTP client connection if - * we are awaiting payment before giving up? - */ -#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MINUTES, 30) - /** - * Return the current recoverydocument of @a account on @a connection - * using @a default_http_status on success. + * Return the current recoverydocument of @a account on @a connection. * * @param connection MHD connection to use * @param account_pub account to query @@ -130,25 +122,30 @@ return_policy (struct MHD_Connection *connection, { char *sig_s; char *etag; + char *etagq; sig_s = GNUNET_STRINGS_data_to_string_alloc (&account_sig, sizeof (account_sig)); - etag = GNUNET_STRINGS_data_to_string_alloc (&recovery_data_hash, - sizeof (recovery_data_hash)); GNUNET_break (MHD_YES == MHD_add_response_header (resp, ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE, sig_s)); + GNUNET_free (sig_s); GNUNET_break (MHD_YES == MHD_add_response_header (resp, ANASTASIS_HTTP_HEADER_POLICY_VERSION, version_s)); + etag = GNUNET_STRINGS_data_to_string_alloc (&recovery_data_hash, + sizeof (recovery_data_hash)); + GNUNET_asprintf (&etagq, + "\"%s\"", + etag); + GNUNET_free (etag); GNUNET_break (MHD_YES == MHD_add_response_header (resp, MHD_HTTP_HEADER_ETAG, - etag)); - GNUNET_free (etag); - GNUNET_free (sig_s); + etagq)); + GNUNET_free (etagq); } { MHD_RESULT ret; @@ -170,7 +167,7 @@ AH_policy_get (struct MHD_Connection *connection, enum ANASTASIS_DB_AccountStatus as; MHD_RESULT ret; uint32_t version; - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; as = db->lookup_account (db->cls, account_pub, @@ -211,13 +208,16 @@ AH_policy_get (struct MHD_Connection *connection, inm = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH); - if (NULL != inm) + if ( (NULL != inm) && + (2 < strlen (inm)) && + ('"' == inm[0]) && + ('"' == inm[strlen (inm) - 1]) ) { struct GNUNET_HashCode inm_h; if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (inm, - strlen (inm), + GNUNET_STRINGS_string_to_data (inm + 1, + strlen (inm) - 2, &inm_h, sizeof (inm_h))) { diff --git a/src/backend/anastasis-httpd_policy.h b/src/backend/anastasis-httpd_policy.h index 33d51cf..2735db6 100644 --- a/src/backend/anastasis-httpd_policy.h +++ b/src/backend/anastasis-httpd_policy.h @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2014, 2015, 2016 Anastasis SARL + Copyright (C) 2019-2021 Anastasis SARL Anastasis 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 diff --git a/src/backend/anastasis-httpd_terms.c b/src/backend/anastasis-httpd_terms.c index b4debe7..94a6380 100644 --- a/src/backend/anastasis-httpd_terms.c +++ b/src/backend/anastasis-httpd_terms.c @@ -3,7 +3,7 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -36,13 +36,6 @@ static struct TALER_MHD_Legal *tos; static struct TALER_MHD_Legal *pp; -/** - * Manages a /terms call. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @return MHD result code - */ MHD_RESULT AH_handler_terms (struct AH_RequestHandler *rh, struct MHD_Connection *connection) @@ -53,15 +46,8 @@ AH_handler_terms (struct AH_RequestHandler *rh, } -/** - * Handle a "/privacy" request. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @return MHD result code - */ MHD_RESULT -AH_handler_privacy (const struct AH_RequestHandler *rh, +AH_handler_privacy (struct AH_RequestHandler *rh, struct MHD_Connection *connection) { (void) rh; @@ -70,11 +56,6 @@ AH_handler_privacy (const struct AH_RequestHandler *rh, } -/** - * Load our terms of service as per configuration. - * - * @param cfg configuration to process - */ void AH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg) { diff --git a/src/backend/anastasis-httpd_terms.h b/src/backend/anastasis-httpd_terms.h index e34c86e..5043c57 100644 --- a/src/backend/anastasis-httpd_terms.h +++ b/src/backend/anastasis-httpd_terms.h @@ -45,7 +45,7 @@ AH_handler_terms (struct AH_RequestHandler *rh, * @return MHD result code */ MHD_RESULT -AH_handler_privacy (const struct AH_RequestHandler *rh, +AH_handler_privacy (struct AH_RequestHandler *rh, struct MHD_Connection *connection); /** diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth-challenge.c index 4dd3ddc..a7d138f 100644 --- a/src/backend/anastasis-httpd_truth.c +++ b/src/backend/anastasis-httpd_truth-challenge.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2019, 2021 Anastasis SARL + Copyright (C) 2019-2022 Anastasis SARL Anastasis 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 @@ -14,8 +14,8 @@ Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ /** - * @file anastasis-httpd_truth.c - * @brief functions to handle incoming requests on /truth + * @file anastasis-httpd_truth-challenge.c + * @brief functions to handle incoming requests on /truth/$TID/challenge * @author Dennis Neufeld * @author Dominik Meister * @author Christian Grothoff @@ -29,6 +29,7 @@ #include "anastasis_authorization_lib.h" #include <taler/taler_merchant_service.h> #include <taler/taler_json_lib.h> +#include <taler/taler_mhd_lib.h> /** * What is the maximum frequency at which we allow @@ -38,18 +39,17 @@ GNUNET_TIME_UNIT_SECONDS, 30) /** - * How long do we hold an HTTP client connection if - * we are awaiting payment before giving up? - */ -#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MINUTES, 30) - -/** * How long should the wallet check for auto-refunds before giving up? */ #define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MINUTES, 2) +/** + * How long should the wallet check for payment before giving up? + */ +#define PAYMENT_TIMEOUT GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_SECONDS, 15) + /** * How many retries do we allow per code? @@ -57,7 +57,7 @@ #define INITIAL_RETRY_COUNTER 3 -struct GetContext +struct ChallengeContext { /** @@ -86,14 +86,24 @@ struct GetContext struct TM_HandlerContext *hc; /** + * Opaque parsing context. + */ + void *opaque_post_parsing_context; + + /** + * Uploaded JSON data, NULL if upload is not yet complete. + */ + json_t *root; + + /** * Kept in DLL for shutdown handling while suspended. */ - struct GetContext *next; + struct ChallengeContext *next; /** * Kept in DLL for shutdown handling while suspended. */ - struct GetContext *prev; + struct ChallengeContext *prev; /** * Connection handle for closing or resuming @@ -131,13 +141,7 @@ struct GetContext struct GNUNET_CONTAINER_HeapNode *hn; /** - * Challenge response we got from the request. - */ - struct GNUNET_HashCode challenge_response; - - /** - * How long do we wait at most for payment or - * authorization? + * When should this request time out? */ struct GNUNET_TIME_Absolute timeout; @@ -152,9 +156,9 @@ struct GetContext unsigned int response_code; /** - * true if client provided a payment secret / order ID? + * true if client did not provide a payment secret / order ID. */ - bool payment_identifier_provided; + bool no_payment_identifier_provided; /** * True if this entry is in the #gc_head DLL. @@ -166,13 +170,9 @@ struct GetContext */ bool suspended; - /** - * Did the request include a response? - */ - bool have_response; - }; + /** * Information we track for refunds. */ @@ -223,12 +223,12 @@ static struct RefundEntry *re_tail; /** * Head of linked list over all authorization processes */ -static struct GetContext *gc_head; +static struct ChallengeContext *gc_head; /** * Tail of linked list over all authorization processes */ -static struct GetContext *gc_tail; +static struct ChallengeContext *gc_tail; /** * Task running #do_timeout(). @@ -237,6 +237,27 @@ static struct GNUNET_SCHEDULER_Task *to_task; /** + * Generate a response telling the client that answering this + * challenge failed because the rate limit has been exceeded. + * + * @param gc request to answer for + * @return MHD status code + */ +static MHD_RESULT +reply_rate_limited (const struct ChallengeContext *gc) +{ + return TALER_MHD_REPLY_JSON_PACK ( + gc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), + GNUNET_JSON_pack_uint64 ("request_limit", + gc->authorization->retry_counter), + GNUNET_JSON_pack_time_rel ("request_frequency", + gc->authorization->code_rotation_period)); +} + + +/** * Timeout requests that are past their due date. * * @param cls NULL @@ -244,7 +265,7 @@ static struct GNUNET_SCHEDULER_Task *to_task; static void do_timeout (void *cls) { - struct GetContext *gc; + struct ChallengeContext *gc; (void) cls; to_task = NULL; @@ -276,9 +297,9 @@ do_timeout (void *cls) void -AH_truth_shutdown (void) +AH_truth_challenge_shutdown (void) { - struct GetContext *gc; + struct ChallengeContext *gc; struct RefundEntry *re; while (NULL != (re = re_head)) @@ -339,22 +360,17 @@ AH_truth_shutdown (void) * Callback to process a POST /orders/ID/refund request * * @param cls closure with a `struct RefundEntry *` - * @param hr HTTP response details - * @param taler_refund_uri the refund uri offered to the wallet - * @param h_contract hash of the contract a Browser may need to authorize - * obtaining the HTTP response. + * @param rr response details */ static void refund_cb ( void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const char *taler_refund_uri, - const struct GNUNET_HashCode *h_contract) + const struct TALER_MERCHANT_RefundResponse *rr) { struct RefundEntry *re = cls; re->ro = NULL; - switch (hr->http_status) + switch (rr->hr.http_status) { case MHD_HTTP_OK: { @@ -386,9 +402,9 @@ refund_cb ( GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Refund `%s' failed with HTTP status %u: %s (#%u)\n", re->order_id, - hr->http_status, - hr->hint, - (unsigned int) hr->ec); + rr->hr.http_status, + rr->hr.hint, + (unsigned int) rr->hr.ec); break; } GNUNET_CONTAINER_DLL_remove (re_head, @@ -405,7 +421,7 @@ refund_cb ( * @param gc request where we failed and should now grant a refund for */ static void -begin_refund (const struct GetContext *gc) +begin_refund (const struct ChallengeContext *gc) { struct RefundEntry *re; @@ -447,7 +463,7 @@ begin_refund (const struct GetContext *gc) static void request_done (struct TM_HandlerContext *hc) { - struct GetContext *gc = hc->ctx; + struct ChallengeContext *gc = hc->ctx; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Request completed\n"); @@ -484,6 +500,12 @@ request_done (struct TM_HandlerContext *hc) TALER_MERCHANT_orders_post_cancel (gc->po); gc->po = NULL; } + if (NULL != gc->root) + { + json_decref (gc->root); + gc->root = NULL; + } + TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context); GNUNET_free (gc); hc->ctx = NULL; } @@ -495,7 +517,7 @@ request_done (struct TM_HandlerContext *hc) * @param gc context to make payment request for */ static void -make_payment_request (struct GetContext *gc) +make_payment_request (struct ChallengeContext *gc) { struct MHD_Response *resp; @@ -559,14 +581,14 @@ make_payment_request (struct GetContext *gc) * Callbacks of this type are used to serve the result of submitting a * /contract request to a merchant. * - * @param cls our `struct GetContext` + * @param cls our `struct ChallengeContext` * @param por response details */ static void proposal_cb (void *cls, const struct TALER_MERCHANT_PostOrdersReply *por) { - struct GetContext *gc = cls; + struct ChallengeContext *gc = cls; enum GNUNET_DB_QueryStatus qs; gc->po = NULL; @@ -622,17 +644,16 @@ proposal_cb (void *cls, /** * Callback to process a GET /check-payment request * - * @param cls our `struct GetContext` - * @param hr HTTP response details + * @param cls our `struct ChallengeContext` * @param osr order status */ static void check_payment_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, const struct TALER_MERCHANT_OrderStatusResponse *osr) { - struct GetContext *gc = cls; + struct ChallengeContext *gc = cls; + const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; gc->cpo = NULL; GNUNET_assert (gc->in_list); @@ -686,7 +707,8 @@ check_payment_cb (void *cls, } } - switch (osr->status) + GNUNET_assert (MHD_HTTP_OK == hr->http_status); + switch (osr->details.ok.status) { case TALER_MERCHANT_OSC_PAID: { @@ -729,7 +751,7 @@ check_payment_cb (void *cls, * @return MHD status code */ static MHD_RESULT -begin_payment (struct GetContext *gc) +begin_payment (struct ChallengeContext *gc) { enum GNUNET_DB_QueryStatus qs; char *order_id; @@ -769,7 +791,6 @@ begin_payment (struct GetContext *gc) AH_backend_url, order_id, NULL /* NOT session-bound */, - false, timeout, &check_payment_cb, gc); @@ -777,8 +798,9 @@ begin_payment (struct GetContext *gc) else { /* Create a fresh order */ + static const char *no_uuids[1] = { NULL }; json_t *order; - struct GNUNET_TIME_Absolute pay_deadline; + struct GNUNET_TIME_Timestamp pay_deadline; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &gc->payment_identifier, @@ -789,9 +811,8 @@ begin_payment (struct GetContext *gc) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Creating fresh order `%s'\n", order_id); - pay_deadline = GNUNET_TIME_relative_to_absolute ( + pay_deadline = GNUNET_TIME_relative_to_timestamp ( ANASTASIS_CHALLENGE_OFFER_LIFETIME); - GNUNET_TIME_round_abs (&pay_deadline); order = GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", &gc->challenge_cost), @@ -801,8 +822,8 @@ begin_payment (struct GetContext *gc) order_id), GNUNET_JSON_pack_time_rel ("auto_refund", AUTO_REFUND_TIMEOUT), - GNUNET_JSON_pack_time_abs ("pay_deadline", - pay_deadline)); + GNUNET_JSON_pack_timestamp ("pay_deadline", + pay_deadline)); gc->po = TALER_MERCHANT_orders_post2 (AH_ctx, AH_backend_url, order, @@ -811,7 +832,7 @@ begin_payment (struct GetContext *gc) 0, NULL, /* no inventory products */ 0, - NULL, /* no uuids */ + no_uuids, /* no uuids */ false, /* do NOT require claim token */ &proposal_cb, gc); @@ -824,59 +845,33 @@ begin_payment (struct GetContext *gc) /** - * Load encrypted keyshare from db and return it to the client. + * Mark @a gc as suspended and update the respective + * data structures and jobs. * - * @param truth_uuid UUID to the truth for the looup - * @param connection the connection to respond upon - * @return MHD status code + * @param[in,out] gc context of the suspended operation */ -static MHD_RESULT -return_key_share ( - const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - struct MHD_Connection *connection) +static void +gc_suspended (struct ChallengeContext *gc) { - struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare; - + gc->suspended = true; + if (NULL == AH_to_heap) + AH_to_heap = GNUNET_CONTAINER_heap_create ( + GNUNET_CONTAINER_HEAP_ORDER_MIN); + gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, + gc, + gc->timeout.abs_value_us); + if (NULL != to_task) { - enum GNUNET_DB_QueryStatus qs; - - qs = db->get_key_share (db->cls, - truth_uuid, - &encrypted_keyshare); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get key share"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } + GNUNET_SCHEDULER_cancel (to_task); + to_task = NULL; } - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning key share\n"); { - struct MHD_Response *resp; - MHD_RESULT ret; - - resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare), - &encrypted_keyshare, - MHD_RESPMEM_MUST_COPY); - TALER_MHD_add_global_headers (resp); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - return ret; + struct ChallengeContext *rn; + + rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); + to_task = GNUNET_SCHEDULER_add_at (rn->timeout, + &do_timeout, + NULL); } } @@ -890,21 +885,30 @@ return_key_share ( */ static MHD_RESULT run_authorization_process (struct MHD_Connection *connection, - struct GetContext *gc) + struct ChallengeContext *gc) { - enum ANASTASIS_AUTHORIZATION_Result ret; + enum ANASTASIS_AUTHORIZATION_ChallengeResult ret; enum GNUNET_DB_QueryStatus qs; GNUNET_assert (! gc->suspended); - ret = gc->authorization->process (gc->as, - gc->timeout, - connection); + if (NULL == gc->authorization->challenge) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + "challenge method not implemented for authorization method"); + } + ret = gc->authorization->challenge (gc->as, + connection); switch (ret) { - case ANASTASIS_AUTHORIZATION_RES_SUCCESS: + case ANASTASIS_AUTHORIZATION_CRES_SUCCESS: /* Challenge sent successfully */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Authorization request sent successfully\n"); + "Authorization request %llu for %s sent successfully\n", + (unsigned long long) gc->code, + TALER_B2S (&gc->truth_uuid)); qs = db->mark_challenge_sent (db->cls, &gc->payment_identifier, &gc->truth_uuid, @@ -913,38 +917,19 @@ run_authorization_process (struct MHD_Connection *connection, gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_FAILED: - if (gc->payment_identifier_provided) + case ANASTASIS_AUTHORIZATION_CRES_FAILED: + if (! gc->no_payment_identifier_provided) { begin_refund (gc); } gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: + case ANASTASIS_AUTHORIZATION_CRES_SUSPENDED: /* connection was suspended */ - gc->suspended = true; - if (NULL == AH_to_heap) - AH_to_heap = GNUNET_CONTAINER_heap_create ( - GNUNET_CONTAINER_HEAP_ORDER_MIN); - gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, - gc, - gc->timeout.abs_value_us); - if (NULL != to_task) - { - GNUNET_SCHEDULER_cancel (to_task); - to_task = NULL; - } - { - struct GetContext *rn; - - rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); - to_task = GNUNET_SCHEDULER_add_at (rn->timeout, - &do_timeout, - NULL); - } + gc_suspended (gc); return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: + case ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED: /* Challenge sent successfully */ qs = db->mark_challenge_sent (db->cls, &gc->payment_identifier, @@ -954,24 +939,10 @@ run_authorization_process (struct MHD_Connection *connection, gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_NO; - case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED: + case ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED: gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_NO; - case ANASTASIS_AUTHORIZATION_RES_FINISHED: - GNUNET_assert (! gc->suspended); - gc->authorization->cleanup (gc->as); - gc->as = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming with authorization successful!\n"); - if (gc->in_list) - { - GNUNET_CONTAINER_DLL_remove (gc_head, - gc_tail, - gc); - gc->in_list = false; - } - return MHD_YES; } GNUNET_break (0); return MHD_NO; @@ -979,141 +950,31 @@ run_authorization_process (struct MHD_Connection *connection, MHD_RESULT -AH_handler_truth_get ( +AH_handler_truth_challenge ( struct MHD_Connection *connection, + struct TM_HandlerContext *hc, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - struct TM_HandlerContext *hc) + const char *upload_data, + size_t *upload_data_size) { - struct GetContext *gc = hc->ctx; + struct ChallengeContext *gc = hc->ctx; void *encrypted_truth; size_t encrypted_truth_size; void *decrypted_truth; size_t decrypted_truth_size; char *truth_mime = NULL; - bool is_question; if (NULL == gc) { /* Fresh request, do initial setup */ - gc = GNUNET_new (struct GetContext); + gc = GNUNET_new (struct ChallengeContext); gc->hc = hc; hc->ctx = gc; gc->connection = connection; gc->truth_uuid = *truth_uuid; gc->hc->cc = &request_done; - { - const char *pay_id; - - pay_id = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - if (NULL != pay_id) - { - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - pay_id, - strlen (pay_id), - &gc->payment_identifier, - sizeof (struct ANASTASIS_PaymentSecretP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - } - gc->payment_identifier_provided = true; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Client provided payment identifier `%s'\n", - pay_id); - } - } - - { - /* check if header contains Truth-Decryption-Key */ - const char *tdk; - - tdk = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); - if (NULL == tdk) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); - } - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - tdk, - strlen (tdk), - &gc->truth_key, - sizeof (struct ANASTASIS_CRYPTO_TruthKeyP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); - } - } - - { - const char *challenge_response_s; - - challenge_response_s = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "response"); - if ( (NULL != challenge_response_s) && - (GNUNET_OK != - GNUNET_CRYPTO_hash_from_string (challenge_response_s, - &gc->challenge_response)) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "response"); - } - gc->have_response = (NULL != challenge_response_s); - } - - { - const char *long_poll_timeout_ms; - - long_poll_timeout_ms = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != long_poll_timeout_ms) - { - unsigned int timeout; - char dummy; - - if (1 != sscanf (long_poll_timeout_ms, - "%u%c", - &timeout, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms (must be non-negative number)"); - } - gc->timeout - = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( - GNUNET_TIME_UNIT_MILLISECONDS, - timeout)); - } - else - { - gc->timeout = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_UNIT_SECONDS); - } - } - + gc->timeout = GNUNET_TIME_relative_to_absolute ( + PAYMENT_TIMEOUT); } /* end of first-time initialization (if NULL == gc) */ else { @@ -1149,6 +1010,61 @@ AH_handler_truth_get ( was indeed paid! */ } + /* parse byte stream upload into JSON */ + if (NULL == gc->root) + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_post_json (connection, + &gc->opaque_post_parsing_context, + upload_data, + upload_data_size, + &gc->root); + if (GNUNET_SYSERR == res) + { + GNUNET_assert (NULL == gc->root); + return MHD_NO; /* bad upload, could not even generate error */ + } + if ( (GNUNET_NO == res) || + (NULL == gc->root) ) + { + GNUNET_assert (NULL == gc->root); + return MHD_YES; /* so far incomplete upload or parser error */ + } + + /* 'root' is now initialized */ + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("truth_decryption_key", + &gc->truth_key), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("payment_secret", + &gc->payment_identifier), + &gc->no_payment_identifier_provided), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + gc->root, + spec); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + if (! gc->no_payment_identifier_provided) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client provided payment identifier `%s'\n", + TALER_B2S (&gc->payment_identifier)); + } + } + { /* load encrypted truth from DB */ enum GNUNET_DB_QueryStatus qs; @@ -1177,39 +1093,58 @@ AH_handler_truth_get ( case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } - is_question = (0 == strcmp ("question", - method)); - if (! is_question) + if (0 == strcmp ("question", + method)) { - gc->authorization - = ANASTASIS_authorization_plugin_load (method, - db, - AH_cfg); - if (NULL == gc->authorization) - { - MHD_RESULT ret; + GNUNET_break_op (0); + GNUNET_free (encrypted_truth); + GNUNET_free (truth_mime); + GNUNET_free (method); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, + "question"); + } - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, - method); - GNUNET_free (encrypted_truth); - GNUNET_free (truth_mime); - GNUNET_free (method); - return ret; - } - gc->challenge_cost = gc->authorization->cost; + gc->authorization + = ANASTASIS_authorization_plugin_load (method, + db, + AH_cfg); + if (NULL == gc->authorization) + { + MHD_RESULT ret; + + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, + method); + GNUNET_free (encrypted_truth); + GNUNET_free (truth_mime); + GNUNET_free (method); + return ret; } - else + + if (gc->authorization->user_provided_code) { - gc->challenge_cost = AH_question_cost; + MHD_RESULT ret; + + GNUNET_break_op (0); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, + method); + GNUNET_free (encrypted_truth); + GNUNET_free (truth_mime); + GNUNET_free (method); + return ret; } + + gc->challenge_cost = gc->authorization->cost; GNUNET_free (method); } - if ( (is_question) || - (! gc->authorization->payment_plugin_managed) ) + if (! gc->authorization->payment_plugin_managed) { if (! TALER_amount_is_zero (&gc->challenge_cost)) { @@ -1217,7 +1152,7 @@ AH_handler_truth_get ( enum GNUNET_DB_QueryStatus qs; bool paid; - if (! gc->payment_identifier_provided) + if (gc->no_payment_identifier_provided) { GNUNET_free (truth_mime); GNUNET_free (encrypted_truth); @@ -1282,246 +1217,86 @@ AH_handler_truth_get ( { GNUNET_free (truth_mime); return TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, NULL); } - /* Special case for secure question: we do not generate a numeric challenge, - but check that the hash matches */ - if (is_question) + /* Not security question and no answer: use plugin to check if + decrypted truth is a valid challenge! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No challenge provided, creating fresh challenge\n"); { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling security question challenge\n"); - if (! gc->have_response) - { - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED, - NULL); - } + enum GNUNET_GenericReturnValue ret; + ret = gc->authorization->validate (gc->authorization->cls, + connection, + truth_mime, + decrypted_truth, + decrypted_truth_size); + GNUNET_free (truth_mime); + switch (ret) { - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Absolute rt; - uint64_t code; - enum ANASTASIS_DB_CodeStatus cs; - struct GNUNET_HashCode hc; - bool satisfied; - uint64_t dummy; - - rt = GNUNET_TIME_UNIT_FOREVER_ABS; - qs = db->create_challenge_code (db->cls, - &gc->truth_uuid, - MAX_QUESTION_FREQ, - GNUNET_TIME_UNIT_HOURS, - INITIAL_RETRY_COUNTER, - &rt, - &code); - if (0 > qs) - { - GNUNET_break (0 < qs); - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "create_challenge_code (for rate limiting)"); - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL); - } - /* decrement trial counter */ - ANASTASIS_hash_answer (code + 1, /* always use wrong answer */ - &hc); - cs = db->verify_challenge_code (db->cls, - &gc->truth_uuid, - &hc, - &dummy, - &satisfied); - switch (cs) - { - case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: - /* good, what we wanted */ - break; - case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: - case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "verify_challenge_code"); - case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL); - case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: - /* this should be impossible, we used code+1 */ - GNUNET_assert (0); - } - } - if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) || - (0 != memcmp (&gc->challenge_response, - decrypted_truth, - decrypted_truth_size)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Wrong answer provided to secure question had %u bytes, wanted %u\n", - (unsigned int) decrypted_truth_size, - (unsigned int) sizeof (struct GNUNET_HashCode)); + case GNUNET_OK: + /* data valid, continued below */ + break; + case GNUNET_NO: + /* data invalid, reply was queued */ GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, - NULL); + return MHD_YES; + case GNUNET_SYSERR: + /* data invalid, reply was NOT queued */ + GNUNET_free (decrypted_truth); + return MHD_NO; } - GNUNET_free (decrypted_truth); - GNUNET_free (truth_mime); - return return_key_share (&gc->truth_uuid, - connection); } - /* Not security question, check for answer in DB */ - if (gc->have_response) + /* Setup challenge and begin authorization process */ { - enum ANASTASIS_DB_CodeStatus cs; - bool satisfied; - uint64_t code; + struct GNUNET_TIME_Timestamp transmission_date; + enum GNUNET_DB_QueryStatus qs; - GNUNET_free (truth_mime); - cs = db->verify_challenge_code (db->cls, + qs = db->create_challenge_code (db->cls, &gc->truth_uuid, - &gc->challenge_response, - &code, - &satisfied); - switch (cs) + gc->authorization->code_rotation_period, + gc->authorization->code_validity_period, + gc->authorization->retry_counter, + &transmission_date, + &gc->code); + switch (qs) { - case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Provided response does not match our stored challenge\n"); - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, - NULL); - case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: - case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); GNUNET_free (decrypted_truth); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "verify_challenge_code"); - case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Response code unknown (possibly expired). Testing if we may provide a new one.\n"); - gc->have_response = false; - break; - case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: + "create_challenge_code"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* 0 == retry_counter of existing challenge => rate limit exceeded */ + GNUNET_free (decrypted_truth); + return reply_rate_limited (gc); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* challenge code was stored successfully*/ GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Response code valid (%s)\n", - satisfied ? "satisfied" : "unsatisfied"); - if (satisfied) - { - GNUNET_free (decrypted_truth); - return return_key_share (&gc->truth_uuid, - connection); - } - /* continue with authorization plugin below */ - gc->code = code; + "Created fresh challenge\n"); break; - default: - GNUNET_break (0); - return MHD_NO; } - } - if (! gc->have_response) - { - /* Not security question and no answer: use plugin to check if - decrypted truth is a valid challenge! */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No challenge provided, creating fresh challenge\n"); - { - enum GNUNET_GenericReturnValue ret; - ret = gc->authorization->validate (gc->authorization->cls, - connection, - truth_mime, - decrypted_truth, - decrypted_truth_size); - GNUNET_free (truth_mime); - switch (ret) - { - case GNUNET_OK: - /* data valid, continued below */ - break; - case GNUNET_NO: - /* data invalid, reply was queued */ - GNUNET_free (decrypted_truth); - return MHD_YES; - case GNUNET_SYSERR: - /* data invalid, reply was NOT queued */ - GNUNET_free (decrypted_truth); - return MHD_NO; - } - } - - /* Setup challenge and begin authorization process */ + if (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_duration ( + transmission_date.abs_time), + <, + gc->authorization->code_retransmission_frequency) ) { - struct GNUNET_TIME_Absolute transmission_date; - enum GNUNET_DB_QueryStatus qs; - - qs = db->create_challenge_code (db->cls, - &gc->truth_uuid, - gc->authorization->code_rotation_period, - gc->authorization->code_validity_period, - gc->authorization->retry_counter, - &transmission_date, - &gc->code); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "create_challenge_code"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* 0 == retry_counter of existing challenge => rate limit exceeded */ - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* challenge code was stored successfully*/ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Created fresh challenge\n"); - break; - } - - if (GNUNET_TIME_absolute_get_duration (transmission_date).rel_value_us < - gc->authorization->code_retransmission_frequency.rel_value_us) - { - /* Too early for a retransmission! */ - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_ALREADY_REPORTED, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_ACTIVE, - NULL); - } + /* Too early for a retransmission! */ + GNUNET_free (decrypted_truth); + return TALER_MHD_REPLY_JSON_PACK ( + gc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("challenge_type", + "TAN_ALREADY_SENT")); } } diff --git a/src/backend/anastasis-httpd_truth-solve.c b/src/backend/anastasis-httpd_truth-solve.c new file mode 100644 index 0000000..eb09dc7 --- /dev/null +++ b/src/backend/anastasis-httpd_truth-solve.c @@ -0,0 +1,1474 @@ +/* + This file is part of Anastasis + Copyright (C) 2019-2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file anastasis-httpd_truth-solve.c + * @brief functions to handle incoming requests on /truth/$TID/solve + * @author Dennis Neufeld + * @author Dominik Meister + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis-httpd.h" +#include "anastasis_service.h" +#include "anastasis-httpd_truth.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_rest_lib.h> +#include "anastasis_authorization_lib.h" +#include <taler/taler_merchant_service.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_mhd_lib.h> + +/** + * What is the maximum frequency at which we allow + * clients to attempt to answer security questions? + */ +#define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * How long should the wallet check for auto-refunds before giving up? + */ +#define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_MINUTES, 2) + + +/** + * How many retries do we allow per code? + */ +#define INITIAL_RETRY_COUNTER 3 + + +struct SolveContext +{ + + /** + * Payment Identifier + */ + struct ANASTASIS_PaymentSecretP payment_identifier; + + /** + * Public key of the challenge which is solved. + */ + struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; + + /** + * Key to decrypt the truth. + */ + struct ANASTASIS_CRYPTO_TruthKeyP truth_key; + + /** + * Cost for paying the challenge. + */ + struct TALER_Amount challenge_cost; + + /** + * Our handler context. + */ + struct TM_HandlerContext *hc; + + /** + * Opaque parsing context. + */ + void *opaque_post_parsing_context; + + /** + * Uploaded JSON data, NULL if upload is not yet complete. + */ + json_t *root; + + /** + * Kept in DLL for shutdown handling while suspended. + */ + struct SolveContext *next; + + /** + * Kept in DLL for shutdown handling while suspended. + */ + struct SolveContext *prev; + + /** + * Connection handle for closing or resuming + */ + struct MHD_Connection *connection; + + /** + * Reference to the authorization plugin which was loaded + */ + struct ANASTASIS_AuthorizationPlugin *authorization; + + /** + * Status of the authorization + */ + struct ANASTASIS_AUTHORIZATION_State *as; + + /** + * Used while we are awaiting proposal creation. + */ + struct TALER_MERCHANT_PostOrdersHandle *po; + + /** + * Used while we are waiting payment. + */ + struct TALER_MERCHANT_OrderMerchantGetHandle *cpo; + + /** + * HTTP response code to use on resume, if non-NULL. + */ + struct MHD_Response *resp; + + /** + * Our entry in the #to_heap, or NULL. + */ + struct GNUNET_CONTAINER_HeapNode *hn; + + /** + * Challenge response we got from the request. + */ + struct GNUNET_HashCode challenge_response; + + /** + * How long do we wait at most for payment or + * authorization? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Random authorization code we are using. + */ + uint64_t code; + + /** + * HTTP response code to use on resume, if resp is set. + */ + unsigned int response_code; + + /** + * true if client did not provide a payment secret / order ID. + */ + bool no_payment_identifier_provided; + + /** + * True if this entry is in the #gc_head DLL. + */ + bool in_list; + + /** + * True if this entry is currently suspended. + */ + bool suspended; + +}; + + +/** + * Head of linked list over all authorization processes + */ +static struct SolveContext *gc_head; + +/** + * Tail of linked list over all authorization processes + */ +static struct SolveContext *gc_tail; + +/** + * Task running #do_timeout(). + */ +static struct GNUNET_SCHEDULER_Task *to_task; + + +/** + * Generate a response telling the client that answering this + * challenge failed because the rate limit has been exceeded. + * + * @param gc request to answer for + * @return MHD status code + */ +static MHD_RESULT +reply_rate_limited (const struct SolveContext *gc) +{ + if (NULL != gc->authorization) + return TALER_MHD_REPLY_JSON_PACK ( + gc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), + GNUNET_JSON_pack_uint64 ("request_limit", + gc->authorization->retry_counter), + GNUNET_JSON_pack_time_rel ("request_frequency", + gc->authorization->code_rotation_period)); + /* must be security question */ + return TALER_MHD_REPLY_JSON_PACK ( + gc->connection, + MHD_HTTP_TOO_MANY_REQUESTS, + TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), + GNUNET_JSON_pack_uint64 ("request_limit", + INITIAL_RETRY_COUNTER), + GNUNET_JSON_pack_time_rel ("request_frequency", + MAX_QUESTION_FREQ)); +} + + +/** + * Timeout requests that are past their due date. + * + * @param cls NULL + */ +static void +do_timeout (void *cls) +{ + struct SolveContext *gc; + + (void) cls; + to_task = NULL; + while (NULL != + (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap))) + { + if (GNUNET_TIME_absolute_is_future (gc->timeout)) + break; + if (gc->suspended) + { + /* Test needed as we may have a "concurrent" + wakeup from another task that did not clear + this entry from the heap before the + response process concluded. */ + gc->suspended = false; + MHD_resume_connection (gc->connection); + } + GNUNET_assert (NULL != gc->hn); + gc->hn = NULL; + GNUNET_assert (gc == + GNUNET_CONTAINER_heap_remove_root (AH_to_heap)); + } + if (NULL == gc) + return; + to_task = GNUNET_SCHEDULER_add_at (gc->timeout, + &do_timeout, + NULL); +} + + +void +AH_truth_solve_shutdown (void) +{ + struct SolveContext *gc; + + while (NULL != (gc = gc_head)) + { + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + gc->in_list = false; + if (NULL != gc->cpo) + { + TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); + gc->cpo = NULL; + } + if (NULL != gc->po) + { + TALER_MERCHANT_orders_post_cancel (gc->po); + gc->po = NULL; + } + if (gc->suspended) + { + gc->suspended = false; + MHD_resume_connection (gc->connection); + } + if (NULL != gc->as) + { + gc->authorization->cleanup (gc->as); + gc->as = NULL; + gc->authorization = NULL; + } + } + ANASTASIS_authorization_plugin_shutdown (); + if (NULL != to_task) + { + GNUNET_SCHEDULER_cancel (to_task); + to_task = NULL; + } +} + + +/** + * Callback used to notify the application about completed requests. + * Cleans up the requests data structures. + * + * @param[in,out] hc + */ +static void +request_done (struct TM_HandlerContext *hc) +{ + struct SolveContext *gc = hc->ctx; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Request completed\n"); + if (NULL == gc) + return; + hc->cc = NULL; + GNUNET_assert (! gc->suspended); + if (gc->in_list) + { + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + gc->in_list = false; + } + if (NULL != gc->hn) + { + GNUNET_assert (gc == + GNUNET_CONTAINER_heap_remove_node (gc->hn)); + gc->hn = NULL; + } + if (NULL != gc->as) + { + gc->authorization->cleanup (gc->as); + gc->authorization = NULL; + gc->as = NULL; + } + if (NULL != gc->cpo) + { + TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); + gc->cpo = NULL; + } + if (NULL != gc->po) + { + TALER_MERCHANT_orders_post_cancel (gc->po); + gc->po = NULL; + } + if (NULL != gc->root) + { + json_decref (gc->root); + gc->root = NULL; + } + TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context); + GNUNET_free (gc); + hc->ctx = NULL; +} + + +/** + * Transmit a payment request for @a order_id on @a connection + * + * @param gc context to make payment request for + */ +static void +make_payment_request (struct SolveContext *gc) +{ + struct MHD_Response *resp; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + GNUNET_assert (NULL != resp); + TALER_MHD_add_global_headers (resp); + { + char *hdr; + char *order_id; + const char *pfx; + const char *hn; + + if (0 == strncasecmp ("https://", + AH_backend_url, + strlen ("https://"))) + { + pfx = "taler://"; + hn = &AH_backend_url[strlen ("https://")]; + } + else if (0 == strncasecmp ("http://", + AH_backend_url, + strlen ("http://"))) + { + pfx = "taler+http://"; + hn = &AH_backend_url[strlen ("http://")]; + } + else + { + /* This invariant holds as per check in anastasis-httpd.c */ + GNUNET_assert (0); + } + /* This invariant holds as per check in anastasis-httpd.c */ + GNUNET_assert (0 != strlen (hn)); + + order_id = GNUNET_STRINGS_data_to_string_alloc ( + &gc->payment_identifier, + sizeof (gc->payment_identifier)); + GNUNET_asprintf (&hdr, + "%spay/%s%s/", + pfx, + hn, + order_id); + GNUNET_free (order_id); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sending payment request `%s'\n", + hdr); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + ANASTASIS_HTTP_HEADER_TALER, + hdr)); + GNUNET_free (hdr); + } + gc->resp = resp; + gc->response_code = MHD_HTTP_PAYMENT_REQUIRED; +} + + +/** + * Callbacks of this type are used to serve the result of submitting a + * /contract request to a merchant. + * + * @param cls our `struct SolveContext` + * @param por response details + */ +static void +proposal_cb (void *cls, + const struct TALER_MERCHANT_PostOrdersReply *por) +{ + struct SolveContext *gc = cls; + enum GNUNET_DB_QueryStatus qs; + + gc->po = NULL; + GNUNET_assert (gc->in_list); + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + gc->in_list = false; + GNUNET_assert (gc->suspended); + gc->suspended = false; + MHD_resume_connection (gc->connection); + AH_trigger_daemon (NULL); + if (MHD_HTTP_OK != por->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Backend returned status %u/%d\n", + por->hr.http_status, + (int) por->hr.ec); + GNUNET_break (0); + gc->resp = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("code", + TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR), + GNUNET_JSON_pack_string ("hint", + "Failed to setup order with merchant backend"), + GNUNET_JSON_pack_uint64 ("backend-ec", + por->hr.ec), + GNUNET_JSON_pack_uint64 ("backend-http-status", + por->hr.http_status), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("backend-reply", + (json_t *) por->hr.reply))); + gc->response_code = MHD_HTTP_BAD_GATEWAY; + return; + } + qs = db->record_challenge_payment (db->cls, + &gc->truth_uuid, + &gc->payment_identifier, + &gc->challenge_cost); + if (0 >= qs) + { + GNUNET_break (0); + gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, + "record challenge payment"); + gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setup fresh order, creating payment request\n"); + make_payment_request (gc); +} + + +/** + * Callback to process a GET /check-payment request + * + * @param cls our `struct SolveContext` + * @param osr order status + */ +static void +check_payment_cb (void *cls, + const struct TALER_MERCHANT_OrderStatusResponse *osr) + +{ + struct SolveContext *gc = cls; + const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; + + gc->cpo = NULL; + GNUNET_assert (gc->in_list); + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + gc->in_list = false; + GNUNET_assert (gc->suspended); + gc->suspended = false; + MHD_resume_connection (gc->connection); + AH_trigger_daemon (NULL); + + switch (hr->http_status) + { + case MHD_HTTP_OK: + GNUNET_assert (NULL != osr); + break; + case MHD_HTTP_NOT_FOUND: + /* We created this order before, how can it be not found now? */ + GNUNET_break (0); + gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED, + NULL); + gc->response_code = MHD_HTTP_BAD_GATEWAY; + return; + case MHD_HTTP_BAD_GATEWAY: + gc->resp = TALER_MHD_make_error ( + TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD, + NULL); + gc->response_code = MHD_HTTP_BAD_GATEWAY; + return; + case MHD_HTTP_GATEWAY_TIMEOUT: + gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT, + "Timeout check payment status"); + GNUNET_assert (NULL != gc->resp); + gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT; + return; + default: + { + char status[14]; + + GNUNET_snprintf (status, + sizeof (status), + "%u", + hr->http_status); + gc->resp = TALER_MHD_make_error ( + TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS, + status); + GNUNET_assert (NULL != gc->resp); + gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + return; + } + } + + GNUNET_assert (MHD_HTTP_OK == hr->http_status); + switch (osr->details.ok.status) + { + case TALER_MERCHANT_OSC_PAID: + { + enum GNUNET_DB_QueryStatus qs; + + qs = db->update_challenge_payment (db->cls, + &gc->truth_uuid, + &gc->payment_identifier); + if (0 <= qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Order has been paid, continuing with request processing\n"); + return; /* continue as planned */ + } + GNUNET_break (0); + gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, + "update challenge payment"); + gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + return; /* continue as planned */ + } + case TALER_MERCHANT_OSC_CLAIMED: + case TALER_MERCHANT_OSC_UNPAID: + /* repeat payment request */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Order remains unpaid, sending payment request again\n"); + make_payment_request (gc); + return; + } + /* should never get here */ + GNUNET_break (0); +} + + +/** + * Helper function used to ask our backend to begin processing a + * payment for the user's account. May perform asynchronous + * operations by suspending the connection if required. + * + * @param gc context to begin payment for. + * @return MHD status code + */ +static MHD_RESULT +begin_payment (struct SolveContext *gc) +{ + enum GNUNET_DB_QueryStatus qs; + char *order_id; + + qs = db->lookup_challenge_payment (db->cls, + &gc->truth_uuid, + &gc->payment_identifier); + if (qs < 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup challenge payment"); + } + GNUNET_assert (! gc->in_list); + gc->in_list = true; + GNUNET_CONTAINER_DLL_insert (gc_tail, + gc_head, + gc); + GNUNET_assert (! gc->suspended); + gc->suspended = true; + MHD_suspend_connection (gc->connection); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + /* We already created the order, check if it was paid */ + struct GNUNET_TIME_Relative timeout; + + order_id = GNUNET_STRINGS_data_to_string_alloc ( + &gc->payment_identifier, + sizeof (gc->payment_identifier)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Order exists, checking payment status for order `%s'\n", + order_id); + timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout); + gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx, + AH_backend_url, + order_id, + NULL /* NOT session-bound */, + timeout, + &check_payment_cb, + gc); + } + else + { + /* Create a fresh order */ + static const char *no_uuids[1] = { NULL }; + json_t *order; + struct GNUNET_TIME_Timestamp pay_deadline; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &gc->payment_identifier, + sizeof (struct ANASTASIS_PaymentSecretP)); + order_id = GNUNET_STRINGS_data_to_string_alloc ( + &gc->payment_identifier, + sizeof (gc->payment_identifier)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Creating fresh order `%s'\n", + order_id); + pay_deadline = GNUNET_TIME_relative_to_timestamp ( + ANASTASIS_CHALLENGE_OFFER_LIFETIME); + order = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &gc->challenge_cost), + GNUNET_JSON_pack_string ("summary", + "challenge fee for anastasis service"), + GNUNET_JSON_pack_string ("order_id", + order_id), + GNUNET_JSON_pack_time_rel ("auto_refund", + AUTO_REFUND_TIMEOUT), + GNUNET_JSON_pack_timestamp ("pay_deadline", + pay_deadline)); + gc->po = TALER_MERCHANT_orders_post2 (AH_ctx, + AH_backend_url, + order, + AUTO_REFUND_TIMEOUT, + NULL, /* no payment target */ + 0, + NULL, /* no inventory products */ + 0, + no_uuids, /* no uuids */ + false, /* do NOT require claim token */ + &proposal_cb, + gc); + json_decref (order); + } + GNUNET_free (order_id); + AH_trigger_curl (); + return MHD_YES; +} + + +/** + * Load encrypted keyshare from db and return it to the client. + * + * @param truth_uuid UUID to the truth for the looup + * @param connection the connection to respond upon + * @return MHD status code + */ +static MHD_RESULT +return_key_share ( + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + struct MHD_Connection *connection) +{ + struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning key share of %s\n", + TALER_B2S (truth_uuid)); + { + enum GNUNET_DB_QueryStatus qs; + + qs = db->get_key_share (db->cls, + truth_uuid, + &encrypted_keyshare); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get key share"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* this should be "impossible", after all the + client was able to solve the challenge! + (Exception: we deleted the truth via GC + just while the client was trying to recover. + Alas, highly unlikely...) */ + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + + { + struct MHD_Response *resp; + MHD_RESULT ret; + + resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare), + &encrypted_keyshare, + MHD_RESPMEM_MUST_COPY); + TALER_MHD_add_global_headers (resp); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + resp); + MHD_destroy_response (resp); + return ret; + } +} + + +/** + * Mark @a gc as suspended and update the respective + * data structures and jobs. + * + * @param[in,out] gc context of the suspended operation + */ +static void +gc_suspended (struct SolveContext *gc) +{ + GNUNET_assert (NULL == gc->hn); + GNUNET_assert (! gc->suspended); + gc->suspended = true; + if (NULL == AH_to_heap) + AH_to_heap = GNUNET_CONTAINER_heap_create ( + GNUNET_CONTAINER_HEAP_ORDER_MIN); + gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, + gc, + gc->timeout.abs_value_us); + if (NULL != to_task) + { + GNUNET_SCHEDULER_cancel (to_task); + to_task = NULL; + } + { + struct SolveContext *rn; + + rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); + to_task = GNUNET_SCHEDULER_add_at (rn->timeout, + &do_timeout, + NULL); + } +} + + +/** + * Run the authorization method-specific 'process' function and continue + * based on its result with generating an HTTP response. + * + * @param connection the connection we are handling + * @param gc our overall handler context + */ +static MHD_RESULT +run_authorization_process (struct MHD_Connection *connection, + struct SolveContext *gc) +{ + enum ANASTASIS_AUTHORIZATION_SolveResult ret; + + GNUNET_assert (! gc->suspended); + if (NULL == gc->authorization->solve) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + "solve method not implemented for authorization method"); + } + ret = gc->authorization->solve (gc->as, + gc->timeout, + &gc->challenge_response, + connection); + switch (ret) + { + case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED: + /* connection was suspended */ + gc_suspended (gc); + return MHD_YES; + case ANASTASIS_AUTHORIZATION_SRES_FAILED: + gc->authorization->cleanup (gc->as); + gc->as = NULL; + return MHD_YES; + case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED: + gc->authorization->cleanup (gc->as); + gc->as = NULL; + return MHD_NO; + case ANASTASIS_AUTHORIZATION_SRES_FINISHED: + GNUNET_assert (! gc->suspended); + gc->authorization->cleanup (gc->as); + gc->as = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming with authorization successful!\n"); + if (gc->in_list) + { + GNUNET_CONTAINER_DLL_remove (gc_head, + gc_tail, + gc); + gc->in_list = false; + } + return MHD_YES; + } + GNUNET_break (0); + return MHD_NO; +} + + +/** + * Use the database to rate-limit queries to the authentication + * procedure, but without actually storing 'real' challenge codes. + * + * @param[in,out] gc context to rate limit requests for + * @return #GNUNET_OK if rate-limiting passes, + * #GNUNET_NO if a reply was sent (rate limited) + * #GNUNET_SYSERR if we failed and no reply + * was queued + */ +static enum GNUNET_GenericReturnValue +rate_limit (struct SolveContext *gc) +{ + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp rt; + uint64_t code; + enum ANASTASIS_DB_CodeStatus cs; + struct GNUNET_HashCode hc; + bool satisfied; + uint64_t dummy; + + rt = GNUNET_TIME_UNIT_FOREVER_TS; + qs = db->create_challenge_code (db->cls, + &gc->truth_uuid, + MAX_QUESTION_FREQ, + GNUNET_TIME_UNIT_HOURS, + INITIAL_RETRY_COUNTER, + &rt, + &code); + if (0 > qs) + { + GNUNET_break (0 < qs); + return (MHD_YES == + TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "create_challenge_code (for rate limiting)")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + return (MHD_YES == + reply_rate_limited (gc)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Using intentionally wrong answer to produce rate-limiting\n"); + /* decrement trial counter */ + ANASTASIS_hash_answer (code + 1, /* always use wrong answer */ + &hc); + cs = db->verify_challenge_code (db->cls, + &gc->truth_uuid, + &hc, + &dummy, + &satisfied); + switch (cs) + { + case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: + /* good, what we wanted */ + return GNUNET_OK; + case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: + case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: + GNUNET_break (0); + return (MHD_YES == + TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "verify_challenge_code")) + ? GNUNET_NO + : GNUNET_SYSERR; + case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: + return (MHD_YES == + reply_rate_limited (gc)) + ? GNUNET_NO + : GNUNET_SYSERR; + case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: + /* this should be impossible, we used code+1 */ + GNUNET_assert (0); + } + return GNUNET_SYSERR; +} + + +/** + * Handle special case of a security question where we do not + * generate a code. Rate limits answers against brute forcing. + * + * @param[in,out] gc request to handle + * @param decrypted_truth hash to check against + * @param decrypted_truth_size number of bytes in @a decrypted_truth + * @return MHD status code + */ +static MHD_RESULT +handle_security_question (struct SolveContext *gc, + const void *decrypted_truth, + size_t decrypted_truth_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling security question challenge\n"); + /* rate limit */ + { + enum GNUNET_GenericReturnValue ret; + + ret = rate_limit (gc); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + /* check reply matches truth */ + if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) || + (0 != memcmp (&gc->challenge_response, + decrypted_truth, + decrypted_truth_size)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Wrong answer provided to secure question had %u bytes, wanted %u\n", + (unsigned int) decrypted_truth_size, + (unsigned int) sizeof (struct GNUNET_HashCode)); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, + NULL); + } + /* good, return the key share */ + return return_key_share (&gc->truth_uuid, + gc->connection); +} + + +/** + * Handle special case of an answer being directly checked by the + * plugin and not by our database. Also ensures that the + * request is rate-limited. + * + * @param[in,out] gc request to handle + * @param decrypted_truth hash to check against + * @param decrypted_truth_size number of bytes in @a decrypted_truth + * @return MHD status code + */ +static MHD_RESULT +direct_validation (struct SolveContext *gc, + const void *decrypted_truth, + size_t decrypted_truth_size) +{ + /* Non-random code, call plugin directly! */ + enum ANASTASIS_AUTHORIZATION_SolveResult aar; + enum GNUNET_GenericReturnValue ret; + + ret = rate_limit (gc); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + gc->as = gc->authorization->start (gc->authorization->cls, + &AH_trigger_daemon, + NULL, + &gc->truth_uuid, + 0LLU, + decrypted_truth, + decrypted_truth_size); + if (NULL == gc->as) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + NULL); + } + if (NULL == gc->authorization->solve) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + "solve method not implemented for authorization method"); + } + aar = gc->authorization->solve (gc->as, + gc->timeout, + &gc->challenge_response, + gc->connection); + switch (aar) + { + case ANASTASIS_AUTHORIZATION_SRES_FAILED: + return MHD_YES; + case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending request handling\n"); + gc_suspended (gc); + return MHD_YES; + case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED: + return MHD_NO; + case ANASTASIS_AUTHORIZATION_SRES_FINISHED: + return return_key_share (&gc->truth_uuid, + gc->connection); + } + GNUNET_break (0); + return MHD_NO; +} + + +/** + * Handle special case of an answer being checked + * by the plugin asynchronously (IBAN) after we inverted + * the hash using the database. + * + * @param[in,out] gc request to handle + * @param code validation code provided by the client + * @param decrypted_truth hash to check against + * @param decrypted_truth_size number of bytes in @a decrypted_truth + * @return MHD status code + */ +static MHD_RESULT +iban_validation (struct SolveContext *gc, + uint64_t code, + const void *decrypted_truth, + size_t decrypted_truth_size) +{ + enum ANASTASIS_AUTHORIZATION_SolveResult aar; + + gc->as = gc->authorization->start (gc->authorization->cls, + &AH_trigger_daemon, + NULL, + &gc->truth_uuid, + code, + decrypted_truth, + decrypted_truth_size); + if (NULL == gc->as) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + NULL); + } + if (NULL == gc->authorization->solve) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, + "solve method not implemented for authorization method"); + } + aar = gc->authorization->solve (gc->as, + gc->timeout, + &gc->challenge_response, + gc->connection); + switch (aar) + { + case ANASTASIS_AUTHORIZATION_SRES_FAILED: + return MHD_YES; + case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending request handling\n"); + gc_suspended (gc); + return MHD_YES; + case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED: + return MHD_NO; + case ANASTASIS_AUTHORIZATION_SRES_FINISHED: + return return_key_share (&gc->truth_uuid, + gc->connection); + } + GNUNET_break (0); + return MHD_NO; +} + + +MHD_RESULT +AH_handler_truth_solve ( + struct MHD_Connection *connection, + struct TM_HandlerContext *hc, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const char *upload_data, + size_t *upload_data_size) +{ + struct SolveContext *gc = hc->ctx; + void *encrypted_truth; + size_t encrypted_truth_size; + void *decrypted_truth; + size_t decrypted_truth_size; + char *truth_mime = NULL; + bool is_question; + + if (NULL == gc) + { + /* Fresh request, do initial setup */ + gc = GNUNET_new (struct SolveContext); + gc->hc = hc; + hc->ctx = gc; + gc->connection = connection; + gc->truth_uuid = *truth_uuid; + gc->hc->cc = &request_done; + gc->timeout = GNUNET_TIME_relative_to_absolute ( + GNUNET_TIME_UNIT_SECONDS); + TALER_MHD_parse_request_timeout (connection, + &gc->timeout); + } /* end of first-time initialization (if NULL == gc) */ + else + { + /* might have been woken up by authorization plugin, + so clear the flag. MDH called us, so we are + clearly no longer suspended */ + gc->suspended = false; + if (NULL != gc->resp) + { + MHD_RESULT ret; + + /* We generated a response asynchronously, queue that */ + ret = MHD_queue_response (connection, + gc->response_code, + gc->resp); + GNUNET_break (MHD_YES == ret); + MHD_destroy_response (gc->resp); + gc->resp = NULL; + return ret; + } + if (NULL != gc->as) + { + /* Authorization process is "running", check what is going on */ + GNUNET_assert (NULL != gc->authorization); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Continuing with running the authorization process\n"); + GNUNET_assert (! gc->suspended); + return run_authorization_process (connection, + gc); + } + /* We get here if the async check for payment said this request + was indeed paid! */ + } + + if (NULL == gc->root) + { + /* parse byte stream upload into JSON */ + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_post_json (connection, + &gc->opaque_post_parsing_context, + upload_data, + upload_data_size, + &gc->root); + if (GNUNET_SYSERR == res) + { + GNUNET_assert (NULL == gc->root); + return MHD_NO; /* bad upload, could not even generate error */ + } + if ( (GNUNET_NO == res) || + (NULL == gc->root) ) + { + GNUNET_assert (NULL == gc->root); + return MHD_YES; /* so far incomplete upload or parser error */ + } + + /* 'root' is now initialized, parse JSON body */ + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("truth_decryption_key", + &gc->truth_key), + GNUNET_JSON_spec_fixed_auto ("h_response", + &gc->challenge_response), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("payment_secret", + &gc->payment_identifier), + &gc->no_payment_identifier_provided), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + gc->root, + spec); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + if (! gc->no_payment_identifier_provided) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client provided payment identifier `%s'\n", + TALER_B2S (&gc->payment_identifier)); + } + } + + { + /* load encrypted truth from DB; we may do this repeatedly + while handling the same request, if payment was checked + asynchronously! */ + enum GNUNET_DB_QueryStatus qs; + char *method; + + qs = db->get_escrow_challenge (db->cls, + &gc->truth_uuid, + &encrypted_truth, + &encrypted_truth_size, + &truth_mime, + &method); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get escrow challenge"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_ANASTASIS_TRUTH_UNKNOWN, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + is_question = (0 == strcmp ("question", + method)); + if (! is_question) + { + gc->authorization + = ANASTASIS_authorization_plugin_load (method, + db, + AH_cfg); + if (NULL == gc->authorization) + { + MHD_RESULT ret; + + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, + method); + GNUNET_free (encrypted_truth); + GNUNET_free (truth_mime); + GNUNET_free (method); + return ret; + } + gc->challenge_cost = gc->authorization->cost; + } + else + { + gc->challenge_cost = AH_question_cost; + } + GNUNET_free (method); + } + + /* check for payment */ + if ( (is_question) || + (! gc->authorization->payment_plugin_managed) ) + { + if (! TALER_amount_is_zero (&gc->challenge_cost)) + { + /* Check database to see if the transaction is paid for */ + enum GNUNET_DB_QueryStatus qs; + bool paid; + + if (gc->no_payment_identifier_provided) + { + GNUNET_free (truth_mime); + GNUNET_free (encrypted_truth); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Beginning payment, client did not provide payment identifier\n"); + return begin_payment (gc); + } + qs = db->check_challenge_payment (db->cls, + &gc->payment_identifier, + &gc->truth_uuid, + &paid); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + GNUNET_free (truth_mime); + GNUNET_free (encrypted_truth); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "check challenge payment"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Create fresh payment identifier (cannot trust client) */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client-provided payment identifier is unknown.\n"); + GNUNET_free (truth_mime); + GNUNET_free (encrypted_truth); + return begin_payment (gc); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + if (! paid) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Payment identifier known. Checking payment with client's payment identifier\n"); + GNUNET_free (truth_mime); + GNUNET_free (encrypted_truth); + return begin_payment (gc); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Payment confirmed\n"); + break; + } + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Request is free of charge\n"); + } + } + + /* We've been paid, now validate the response */ + /* decrypt encrypted_truth */ + ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key, + encrypted_truth, + encrypted_truth_size, + &decrypted_truth, + &decrypted_truth_size); + GNUNET_free (encrypted_truth); + if (NULL == decrypted_truth) + { + /* most likely, the decryption key is simply wrong */ + GNUNET_break_op (0); + GNUNET_free (truth_mime); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, + NULL); + } + + /* Special case for secure question: we do not generate a numeric challenge, + but check that the hash matches */ + if (is_question) + { + MHD_RESULT ret; + + ret = handle_security_question (gc, + decrypted_truth, + decrypted_truth_size); + GNUNET_free (truth_mime); + GNUNET_free (decrypted_truth); + return ret; + } + + /* Not security question, check for answer in DB */ + { + enum ANASTASIS_DB_CodeStatus cs; + bool satisfied = false; + uint64_t code; + + GNUNET_free (truth_mime); + if (gc->authorization->user_provided_code) + { + MHD_RESULT res; + + if (GNUNET_TIME_absolute_is_past (gc->timeout)) + { + GNUNET_free (decrypted_truth); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Timeout with user provided code\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_IBAN_MISSING_TRANSFER, + "timeout awaiting validation"); + } + res = direct_validation (gc, + decrypted_truth, + decrypted_truth_size); + GNUNET_free (decrypted_truth); + return res; + } + + /* random code, check against database */ + cs = db->verify_challenge_code (db->cls, + &gc->truth_uuid, + &gc->challenge_response, + &code, + &satisfied); + switch (cs) + { + case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Provided response does not match our stored challenge\n"); + GNUNET_free (decrypted_truth); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, + NULL); + case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: + case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: + GNUNET_break (0); + GNUNET_free (decrypted_truth); + return TALER_MHD_reply_with_error (gc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "verify_challenge_code"); + case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: + GNUNET_free (decrypted_truth); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Specified challenge code %s was not issued\n", + GNUNET_h2s (&gc->challenge_response)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN, + "specific challenge code was not issued"); + case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: + if (! satisfied) + { + MHD_RESULT res; + + res = iban_validation (gc, + code, + decrypted_truth, + decrypted_truth_size); + GNUNET_free (decrypted_truth); + return res; + } + GNUNET_free (decrypted_truth); + return return_key_share (&gc->truth_uuid, + connection); + default: + GNUNET_break (0); + return MHD_NO; + } + } +} diff --git a/src/backend/anastasis-httpd_truth_upload.c b/src/backend/anastasis-httpd_truth-upload.c index d9e63c3..1c2a58d 100644 --- a/src/backend/anastasis-httpd_truth_upload.c +++ b/src/backend/anastasis-httpd_truth-upload.c @@ -287,15 +287,14 @@ proposal_cb (void *cls, * Callback to process a GET /check-payment request * * @param cls our `struct PolicyUploadContext` - * @param hr HTTP response details * @param osr order status */ static void check_payment_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, const struct TALER_MERCHANT_OrderStatusResponse *osr) { struct TruthUploadContext *tuc = cls; + const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; tuc->cpo = NULL; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -311,7 +310,7 @@ check_payment_cb (void *cls, NULL); break; case MHD_HTTP_OK: - switch (osr->status) + switch (osr->details.ok.status) { case TALER_MERCHANT_OSC_PAID: { @@ -321,13 +320,12 @@ check_payment_cb (void *cls, const json_t *contract; struct TALER_Amount amount; struct GNUNET_JSON_Specification cspec[] = { - TALER_JSON_spec_amount ("amount", - AH_currency, - &amount), + TALER_JSON_spec_amount_any ("amount", + &amount), GNUNET_JSON_spec_end () }; - contract = osr->details.paid.contract_terms; + contract = osr->details.ok.details.paid.contract_terms; if (GNUNET_OK != GNUNET_JSON_parse (contract, cspec, @@ -356,7 +354,7 @@ check_payment_cb (void *cls, qs = db->record_truth_upload_payment ( db->cls, &tuc->truth_uuid, - &osr->details.paid.deposit_total, + &osr->details.ok.details.paid.deposit_total, paid_until); if (qs <= 0) { @@ -398,6 +396,7 @@ check_payment_cb (void *cls, case MHD_HTTP_NOT_FOUND: /* Setup fresh order */ { + static const char *no_uuids[1] = { NULL }; char *order_id; json_t *order; @@ -417,7 +416,6 @@ check_payment_cb (void *cls, "description", "challenge storage fee", "quantity", (json_int_t) tuc->years_to_pay, "unit", "years", - "order_id", order_id); GNUNET_free (order_id); @@ -429,7 +427,7 @@ check_payment_cb (void *cls, 0, NULL, /* no inventory products */ 0, - NULL, /* no uuids */ + no_uuids, /* no uuids */ false, /* do NOT require claim token */ &proposal_cb, tuc); @@ -487,7 +485,6 @@ begin_payment (struct TruthUploadContext *tuc) AH_backend_url, order_id, NULL /* our payments are NOT session-bound */, - false, timeout, &check_payment_cb, tuc); @@ -519,17 +516,18 @@ AH_handler_truth_post ( struct TruthUploadContext *tuc = hc->ctx; MHD_RESULT ret; int res; - struct ANASTASIS_CRYPTO_EncryptedKeyShareP keyshare_data; + struct ANASTASIS_CRYPTO_EncryptedKeyShareP key_share_data; void *encrypted_truth; size_t encrypted_truth_size; const char *truth_mime = NULL; const char *type; enum GNUNET_DB_QueryStatus qs; uint32_t storage_years; - struct GNUNET_TIME_Absolute paid_until; + struct GNUNET_TIME_Timestamp paid_until + = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("keyshare_data", - &keyshare_data), + GNUNET_JSON_spec_fixed_auto ("key_share_data", + &key_share_data), GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_varsize ("encrypted_truth", @@ -537,7 +535,8 @@ AH_handler_truth_post ( &encrypted_truth_size), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("truth_mime", - &truth_mime)), + &truth_mime), + NULL), GNUNET_JSON_spec_uint32 ("storage_duration_years", &storage_years), GNUNET_JSON_spec_end () @@ -550,78 +549,12 @@ AH_handler_truth_post ( tuc->truth_uuid = *truth_uuid; hc->ctx = tuc; hc->cc = &cleanup_truth_post; - - /* check for excessive upload */ - { - const char *lens; - unsigned long len; - char dummy; - - lens = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONTENT_LENGTH); - if ( (NULL == lens) || - (1 != sscanf (lens, - "%lu%c", - &len, - &dummy)) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - (NULL == lens) - ? TALER_EC_ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH - : TALER_EC_ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH, - NULL); - } - if (len / 1024 / 1024 >= AH_upload_limit_mb) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_PAYLOAD_TOO_LARGE, - TALER_EC_SYNC_MALFORMED_CONTENT_LENGTH, - "Content-length value not acceptable"); - } - } - - { - const char *long_poll_timeout_ms; - - long_poll_timeout_ms = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != long_poll_timeout_ms) - { - unsigned int timeout; - char dummy; - - if (1 != sscanf (long_poll_timeout_ms, - "%u%c", - &timeout, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms (must be non-negative number)"); - } - tuc->timeout - = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( - GNUNET_TIME_UNIT_MILLISECONDS, - timeout)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Long polling for %u ms enabled\n", - timeout); - } - else - { - tuc->timeout = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_UNIT_SECONDS); - } - } - + TALER_MHD_check_content_length (connection, + AH_upload_limit_mb * 1024LLU * 1024LLU); + tuc->timeout = GNUNET_TIME_relative_to_absolute ( + GNUNET_TIME_UNIT_SECONDS); + TALER_MHD_parse_request_timeout (connection, + &tuc->timeout); } /* end 'if (NULL == tuc)' */ if (NULL != tuc->resp) @@ -695,95 +628,89 @@ AH_handler_truth_post ( if (0 == storage_years) storage_years = 1; + if (! TALER_amount_is_zero (&AH_truth_upload_fee)) { - struct TALER_Amount zero_amount; - - TALER_amount_set_zero (AH_currency, - &zero_amount); - if (0 != TALER_amount_cmp (&AH_truth_upload_fee, - &zero_amount)) + struct GNUNET_TIME_Timestamp desired_until; + enum GNUNET_DB_QueryStatus qs; + + desired_until + = GNUNET_TIME_relative_to_timestamp ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, + storage_years)); + qs = db->check_truth_upload_paid (db->cls, + truth_uuid, + &paid_until); + if (qs < 0) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + if ( (0 == qs) || + (GNUNET_TIME_timestamp_cmp (paid_until, + <, + desired_until) ) ) { - struct GNUNET_TIME_Absolute desired_until; - enum GNUNET_DB_QueryStatus qs; - - desired_until - = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, - storage_years)); - qs = db->check_truth_upload_paid (db->cls, - truth_uuid, - &paid_until); - if (qs < 0) + struct GNUNET_TIME_Relative rem; + + if (GNUNET_TIME_absolute_is_past (paid_until.abs_time)) + paid_until = GNUNET_TIME_timestamp_get (); + rem = GNUNET_TIME_absolute_get_difference (paid_until.abs_time, + desired_until.abs_time); + tuc->years_to_pay = rem.rel_value_us + / GNUNET_TIME_UNIT_YEARS.rel_value_us; + if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)) + tuc->years_to_pay++; + if (0 > + TALER_amount_multiply (&tuc->upload_fee, + &AH_truth_upload_fee, + tuc->years_to_pay)) + { + GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); - if ( (0 == qs) || - (paid_until.abs_value_us < desired_until.abs_value_us) ) + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "storage_duration_years"); + } + if (! TALER_amount_is_zero (&tuc->upload_fee)) { - struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Relative rem; - - now = GNUNET_TIME_absolute_get (); - if (paid_until.abs_value_us < now.abs_value_us) - paid_until = now; - rem = GNUNET_TIME_absolute_get_difference (paid_until, - desired_until); - tuc->years_to_pay = rem.rel_value_us - / GNUNET_TIME_UNIT_YEARS.rel_value_us; - if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)) - tuc->years_to_pay++; - if (0 > - TALER_amount_multiply (&tuc->upload_fee, - &AH_truth_upload_fee, - tuc->years_to_pay)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "storage_duration_years"); - } - if ( (0 != tuc->upload_fee.fraction) || - (0 != tuc->upload_fee.value) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Truth upload payment required (%d)!\n", - qs); - return begin_payment (tuc); - } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Truth upload payment required (%d)!\n", + qs); + return begin_payment (tuc); } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "TRUTH paid until %s (%d)!\n", - GNUNET_STRINGS_relative_time_to_string ( - GNUNET_TIME_absolute_get_remaining ( - paid_until), - GNUNET_YES), - qs); - } - else - { - paid_until - = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, - ANASTASIS_MAX_YEARS_STORAGE)); } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "TRUTH paid until %s (%d)!\n", + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + paid_until.abs_time), + GNUNET_YES), + qs); + } + else + { + paid_until + = GNUNET_TIME_relative_to_timestamp ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, + ANASTASIS_MAX_YEARS_STORAGE)); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Storing truth until %s!\n", - GNUNET_STRINGS_absolute_time_to_string (paid_until)); + "Storing truth %s until %s!\n", + TALER_B2S (truth_uuid), + GNUNET_TIME_timestamp2s (paid_until)); qs = db->store_truth (db->cls, truth_uuid, - &keyshare_data, + &key_share_data, (NULL == truth_mime) ? "" : truth_mime, encrypted_truth, encrypted_truth_size, type, - GNUNET_TIME_absolute_get_remaining (paid_until)); + GNUNET_TIME_absolute_get_remaining ( + paid_until.abs_time)); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -813,8 +740,8 @@ AH_handler_truth_post ( ok = ( (xtruth_size == encrypted_truth_size) && (0 == strcmp (xmethod, type)) && - (0 == strcmp (truth_mime, - xtruth_mime)) && + (0 == strcmp (((NULL == truth_mime) ? "" : truth_mime), + ((NULL == xtruth_mime) ? "" : xtruth_mime))) && (0 == memcmp (xtruth, encrypted_truth, xtruth_size)) ); diff --git a/src/backend/anastasis-httpd_truth.h b/src/backend/anastasis-httpd_truth.h index 87e570b..a436394 100644 --- a/src/backend/anastasis-httpd_truth.h +++ b/src/backend/anastasis-httpd_truth.h @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2014, 2015, 2016, 2021 Anastasis SARL + Copyright (C) 2020-2022 Anastasis SARL Anastasis 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 @@ -24,13 +24,18 @@ #define ANASTASIS_HTTPD_TRUTH_H #include <microhttpd.h> - /** - * Prepare all active GET truth requests for system shutdown. + * Prepare all active POST truth solve requests for system shutdown. */ void -AH_truth_shutdown (void); +AH_truth_solve_shutdown (void); + +/** + * Prepare all active POST truth challenge requests for system shutdown. + */ +void +AH_truth_challenge_shutdown (void); /** * Prepare all active POST truth requests for system shutdown. @@ -40,36 +45,60 @@ AH_truth_upload_shutdown (void); /** - * Handle a GET to /truth/$UUID + * Handle a POST to /truth/$UUID. * - * @param connection the MHD connection to handle + * @param[in,out] connection the MHD connection to handle + * @param[in,out] hc connection context * @param truth_uuid the truth UUID - * @param hc connection context + * @param truth_data truth data + * @param truth_data_size number of bytes (left) in @a truth_data * @return MHD result code */ MHD_RESULT -AH_handler_truth_get ( +AH_handler_truth_post ( struct MHD_Connection *connection, + struct TM_HandlerContext *hc, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - struct TM_HandlerContext *hc); + const char *truth_data, + size_t *truth_data_size); /** - * Handle a POST to /truth/$UUID. + * Handle a POST to /truth/$UUID/solve. * - * @param connection the MHD connection to handle - * @param hc connection context + * @param[in,out] connection the MHD connection to handle + * @param[in,out] hc connection context * @param truth_uuid the truth UUID * @param truth_data truth data * @param truth_data_size number of bytes (left) in @a truth_data * @return MHD result code */ MHD_RESULT -AH_handler_truth_post ( +AH_handler_truth_solve ( struct MHD_Connection *connection, struct TM_HandlerContext *hc, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - const char *truth_data, - size_t *truth_data_size); + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a POST to /truth/$UUID/challenge. + * + * @param[in,out] connection the MHD connection to handle + * @param[in,out] hc connection context + * @param truth_uuid the truth UUID + * @param truth_data truth data + * @param truth_data_size number of bytes (left) in @a truth_data + * @return MHD result code + */ +MHD_RESULT +AH_handler_truth_challenge ( + struct MHD_Connection *connection, + struct TM_HandlerContext *hc, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const char *upload_data, + size_t *upload_data_size); + #endif diff --git a/src/backend/anastasis.conf b/src/backend/anastasis.conf index 8d2f0f6..4721760 100644 --- a/src/backend/anastasis.conf +++ b/src/backend/anastasis.conf @@ -49,18 +49,18 @@ UPLOAD_LIMIT_MB = 16 FULFILLMENT_URL = taler://fulfillment-success # Server salt 16 Byte -# SERVER_SALT = gUfO1KGOKYIFlFQg +# PROVIDER_SALT = gUfO1KGOKYIFlFQg # Directory with our terms of service. -TERMS_DIR = $DATADIR/tos/ +TERMS_DIR = ${DATADIR}tos/ # Etag / filename for the terms of service. TERMS_ETAG = 0 # Directory with our privacy policy. -PRIVACY_DIR = $DATADIR/pp/ +PRIVACY_DIR = ${DATADIR}pp/ # Etag / filename for the privacy policy. PRIVACY_ETAG = 0 diff --git a/src/cli/.gitignore b/src/cli/.gitignore index dbf01fa..111e321 100644 --- a/src/cli/.gitignore +++ b/src/cli/.gitignore @@ -1,6 +1,13 @@ *.log +*.err +*.out anastasis-reducer test_reducer_home *.trs taler-bank.err wallet.err +anastasis-discover +talercheck +test_reducer.conf.edited +wallet-withdraw.out +libeufin-transfer-initiate.out diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 8434c91..8b2a9a0 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -2,6 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include bin_PROGRAMS = \ + anastasis-discover \ anastasis-reducer if USE_COVERAGE @@ -19,7 +20,11 @@ check_SCRIPTS = \ test_anastasis_reducer_done_policy_review.sh \ test_anastasis_reducer_enter_secret.sh \ test_anastasis_reducer_recovery_enter_user_attributes.sh \ - test_iban.sh + test_anastasis_reducer_recovery_no_pay.sh \ + test_anastasis_reducer_recovery_hanging.sh + +# Removed for now, libeufin is not yet working OK for this. +# test_iban.sh AM_TESTS_ENVIRONMENT=export ANASTASIS_PREFIX=$${ANASTASIS_PREFIX:-@libdir@};export PATH=$${ANASTASIS_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; @@ -29,12 +34,19 @@ TESTS = \ EXTRA_DIST = \ $(check_SCRIPTS) \ + setup.sh \ + test_reducer_home/.local/share/taler/exchange-offline/master.priv \ test_reducer.conf \ + test_reducer_free.conf \ test_free_reducer.conf \ test_anastasis_reducer_1.conf \ test_anastasis_reducer_2.conf \ test_anastasis_reducer_3.conf \ test_anastasis_reducer_4.conf \ + test_anastasis_reducer_free_1.conf \ + test_anastasis_reducer_free_2.conf \ + test_anastasis_reducer_free_3.conf \ + test_anastasis_reducer_free_4.conf \ resources/00-backup.json \ resources/01-backup.json \ resources/02-backup.json \ @@ -58,3 +70,17 @@ anastasis_reducer_LDADD = \ -lgnunetutil \ -ljansson \ $(XLIB) + + +anastasis_discover_SOURCES = \ + anastasis-cli-discover.c +anastasis_discover_LDADD = \ + $(top_builddir)/src/util/libanastasisutil.la \ + $(top_builddir)/src/reducer/libanastasisredux.la \ + -ltalerjson \ + -ltalerutil \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson \ + $(XLIB) diff --git a/src/cli/anastasis-cli-discover.c b/src/cli/anastasis-cli-discover.c new file mode 100644 index 0000000..f614165 --- /dev/null +++ b/src/cli/anastasis-cli-discover.c @@ -0,0 +1,261 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file cli/anastasis-cli-discover.c + * @brief command line tool to discover recovery policies + * @author Christian Grothoff + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "anastasis_redux.h" +#include <taler/taler_util.h> +#include <taler/taler_error_codes.h> +#include <taler/taler_json_lib.h> +#include "anastasis_util_lib.h" + +/** + * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). + */ +static struct GNUNET_CURL_RescheduleContext *rc; + +/** + * Curl context for communication with anastasis backend + */ +static struct GNUNET_CURL_Context *ctx; + +/** + * Input to -a option given. + */ +static char *input; + +/** + * JSON containing previous state + */ +static json_t *prev_state; + +/** + * JSON containing arguments for action + */ +static json_t *arguments; + +/** + * Handle to an ongoing action. + */ +struct ANASTASIS_PolicyDiscovery *pd; + +/** + * Return value from main. + */ +static int global_ret; + + +/** + * Function called on each discovered recovery policy. Called + * with all arguments NULL if we have received all policies that + * we could possibly receive for the current operation. + * + * The client can then start a new policy discovery process, using the + * smallest (also most recent) @a version received per @a provider_url + * in the cursor to resume. Note that in this case, the application + * logic is responsible for de-duplication using @a hcpd, or it may show + * policies again if they are at different providers under versions not + * queried up to the cursor. + * + * @param cls closure + * @param hcpd hash of the compressed policy document (unique per policy) + * @param provider_url which provider claims to have this policy + * @param version version of the policy at this provider + * @param attribute_mask combination of optional identity attributes + * present in the state that was used to locate this version + * @param server_time when did the provider receive the upload + * @param secret_name name the user assigned to the backup + */ +static void +print_policy_cb (void *cls, + const struct GNUNET_HashCode *hcpd, + const char *provider_url, + uint32_t version, + json_int_t attribute_mask, + struct GNUNET_TIME_Timestamp server_time, + const char *secret_name, + const json_t *providers) +{ + if (NULL == hcpd) + { + fprintf (stderr, + "All results received, terminating\n"); + pd = NULL; + global_ret = 0; + GNUNET_SCHEDULER_shutdown (); + return; + } + fprintf (stdout, + "%s %u %u : \"%s\" \"%s\" (%s)\n", + provider_url, + (unsigned int) version, + (unsigned int) attribute_mask, + GNUNET_TIME_timestamp2s (server_time), + secret_name, + GNUNET_h2s (hcpd)); +} + + +/** + * @brief Shutdown the application. + * + * @param cls closure + */ +static void +shutdown_task (void *cls) +{ + (void) cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Shutdown initiated\n"); + if (NULL != pd) + { + ANASTASIS_policy_discovery_stop (pd); + pd = NULL; + } + ANASTASIS_redux_done (); + if (NULL != ctx) + { + GNUNET_CURL_fini (ctx); + ctx = NULL; + } + if (NULL != rc) + { + GNUNET_CURL_gnunet_rc_destroy (rc); + rc = NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Shutdown complete\n"); +} + + +/** + * @brief Start the application + * + * @param cls closure + * @param args arguments left + * @param cfgfile config file name + * @param cfg handle for the configuration file + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + (void) cls; + json_error_t error; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting anastasis-discover\n"); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); + /* load cursor */ + if (NULL != input) + { + arguments = json_loads (input, + JSON_DECODE_ANY, + &error); + if (NULL == arguments) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Failed to parse arguments on line %u:%u: %s!\n", + error.line, + error.column, + error.text); + GNUNET_SCHEDULER_shutdown (); + return; + } + } + /* load state */ + if (NULL != args[0]) + { + prev_state = json_load_file (args[0], + JSON_DECODE_ANY, + &error); + args++; + } + else + { + prev_state = json_loadf (stdin, + JSON_DECODE_ANY, + &error); + } + if (NULL == prev_state) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Failed to parse initial state on line %u:%u: %s!\n", + error.line, + error.column, + error.text); + GNUNET_SCHEDULER_shutdown (); + return; + } + /* initialize HTTP client event loop */ + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &rc); + rc = GNUNET_CURL_gnunet_rc_create (ctx); + ANASTASIS_redux_init (ctx); + pd = ANASTASIS_policy_discovery_start (prev_state, + arguments, + &print_policy_cb, + NULL); +} + + +int +main (int argc, + char *const *argv) +{ + /* the available command line options */ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('a', + "arguments", + "JSON", + "pass a JSON string containing cursor to use", + &input), + + GNUNET_GETOPT_OPTION_END + }; + enum GNUNET_GenericReturnValue ret; + + /* FIRST get the libtalerutil initialization out + of the way. Then throw that one away, and force + the SYNC defaults to be used! */ + (void) TALER_project_data_default (); + GNUNET_OS_init (ANASTASIS_project_data_default ()); + ret = GNUNET_PROGRAM_run (argc, + argv, + "anastasis-discover", + "This is an application for finding secrets that could be recovered.\n", + options, + &run, + NULL); + if (GNUNET_SYSERR == ret) + return 3; + if (GNUNET_NO == ret) + return 0; + return global_ret; +} + + +/* end of anastasis-cli-discover.c */ diff --git a/src/cli/anastasis-cli-redux.c b/src/cli/anastasis-cli-redux.c index dc9c7ab..e2d2e1d 100644 --- a/src/cli/anastasis-cli-redux.c +++ b/src/cli/anastasis-cli-redux.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2020,2021 Anastasis SARL + Copyright (C) 2020,2021,2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -41,6 +41,12 @@ static struct GNUNET_CURL_RescheduleContext *rc; static struct GNUNET_CURL_Context *ctx; /** + * Application ID to include in the user attributes. + * (-a option). + */ +char *application_id; + +/** * -b option given. */ static int b_flag; @@ -146,6 +152,9 @@ action_cb (void *cls, "Redux failed with error %d: %s\n", error_code, TALER_ErrorCode_get_hint (error_code)); + json_dumpf (result_state, + stderr, + JSON_INDENT (2)); } GNUNET_SCHEDULER_shutdown (); global_ret = (TALER_EC_NONE != error_code) ? 1 : 0; @@ -309,6 +318,21 @@ run (void *cls, &rc); rc = GNUNET_CURL_gnunet_rc_create (ctx); ANASTASIS_redux_init (ctx); + /* Expand identity_attributes if -a is given explicitly and we + are at the respective step of the reduction */ + if ( (0 == strcasecmp (action, + "enter_user_attributes")) && + (NULL != application_id) && + (NULL != arguments) ) + { + json_t *attr = json_object_get (arguments, + "identity_attributes"); + if (NULL != attr) + GNUNET_assert (0 == + json_object_set_new (attr, + "application-id", + json_string (application_id))); + } ra = ANASTASIS_redux_action (prev_state, action, arguments, @@ -324,6 +348,16 @@ main (int argc, { /* the available command line options */ struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('A', + "application", + "ID", + "set the application ID", + &application_id), + GNUNET_GETOPT_option_string ('a', + "arguments", + "JSON", + "pass a JSON string containing arguments to reducer", + &input), GNUNET_GETOPT_option_flag ('b', "backup", "use reducer to handle states for backup process", @@ -332,12 +366,6 @@ main (int argc, "restore", "use reducer to handle states for restore process", &r_flag), - GNUNET_GETOPT_option_string ('a', - "arguments", - "JSON", - "pass a JSON string containing arguments to reducer", - &input), - GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; @@ -354,6 +382,7 @@ main (int argc, options, &run, NULL); + GNUNET_free (application_id); if (GNUNET_SYSERR == ret) return 3; if (GNUNET_NO == ret) diff --git a/src/cli/resources/00-backup.json b/src/cli/resources/00-backup.json index 6e6c320..700f8cd 100644 --- a/src/cli/resources/00-backup.json +++ b/src/cli/resources/00-backup.json @@ -2,7 +2,8 @@ "continents": [ "Europe", "North America", - "Testcontinent" + "Demoworld" ], - "backup_state": "CONTINENT_SELECTING" -}
\ No newline at end of file + "backup_state": "CONTINENT_SELECTING", + "reducer_type": "backup" +} diff --git a/src/cli/resources/00-recovery.json b/src/cli/resources/00-recovery.json index acff19a..e9b14f3 100644 --- a/src/cli/resources/00-recovery.json +++ b/src/cli/resources/00-recovery.json @@ -4,5 +4,6 @@ "North America", "Testcontinent" ], + "reducer_type": "recovery", "recovery_state": "CONTINENT_SELECTING" -}
\ No newline at end of file +} diff --git a/src/cli/resources/01-backup.json b/src/cli/resources/01-backup.json index 842d3af..be2a9d9 100644 --- a/src/cli/resources/01-backup.json +++ b/src/cli/resources/01-backup.json @@ -2,15 +2,16 @@ "continents": [ "Europe", "North America", - "Testcontinent" + "Demoworld" ], "backup_state": "COUNTRY_SELECTING", - "selected_continent": "Testcontinent", + "reducer_type": "backup", + "selected_continent": "Demoworld", "countries": [ { "code": "xx", "name": "Testland", - "continent": "Testcontinent", + "continent": "Demoworld", "continent_i18n": { "xx": "Testkontinent" }, @@ -19,23 +20,7 @@ "de_CH": "Testlandi", "fr": "Testpais", "en": "Testland" - }, - "currency": "TESTKUDOS" - }, - { - "code": "xy", - "name": "Demoland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, - "name_i18n": { - "de_DE": "Demolandt", - "de_CH": "Demolandi", - "fr": "Demopais", - "en": "Demoland" - }, - "currency": "KUDOS" + } } ] -}
\ No newline at end of file +} diff --git a/src/cli/resources/01-recovery.json b/src/cli/resources/01-recovery.json index 11aafd3..5489814 100644 --- a/src/cli/resources/01-recovery.json +++ b/src/cli/resources/01-recovery.json @@ -5,6 +5,7 @@ "Testcontinent" ], "recovery_state": "COUNTRY_SELECTING", + "reducer_type": "recovery", "selected_continent": "Testcontinent", "countries": [ { @@ -38,4 +39,4 @@ "currency": "KUDOS" } ] -}
\ No newline at end of file +} diff --git a/src/cli/resources/02-backup.json b/src/cli/resources/02-backup.json index c9bba16..a298b69 100644 --- a/src/cli/resources/02-backup.json +++ b/src/cli/resources/02-backup.json @@ -2,50 +2,31 @@ "continents": [ "Europe", "North America", - "Testcontinent" + "Demoworld" ], "backup_state": "USER_ATTRIBUTES_COLLECTING", + "reducer_type": "backup", "selected_continent": "Testcontinent", "countries": [ { "code": "xx", "name": "Testland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, + "continent": "Demoworld", "name_i18n": { "de_DE": "Testlandt", "de_CH": "Testlandi", "fr": "Testpais", "en": "Testland" - }, - "currency": "TESTKUDOS" - }, - { - "code": "xy", - "name": "Demoland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, - "name_i18n": { - "de_DE": "Demolandt", - "de_CH": "Demolandi", - "fr": "Demopais", - "en": "Demoland" - }, - "currency": "KUDOS" + } } ], "authentication_providers": { - "http://localhost:8086/": {}, - "http://localhost:8087/": {}, - "http://localhost:8088/": {}, - "http://localhost:8089/": {} + "http://localhost:8086/": { "status" : "not-contacted" }, + "http://localhost:8087/": { "status" : "not-contacted" }, + "http://localhost:8088/": { "status" : "not-contacted" }, + "http://localhost:8089/": { "status" : "not-contacted" } }, "selected_country": "xx", - "currencies": [ "TESTKUDOS" ], "required_attributes": [ { "type": "string", diff --git a/src/cli/resources/02-recovery.json b/src/cli/resources/02-recovery.json index 79cfd6d..875fae0 100644 --- a/src/cli/resources/02-recovery.json +++ b/src/cli/resources/02-recovery.json @@ -5,6 +5,7 @@ "Testcontinent" ], "recovery_state": "USER_ATTRIBUTES_COLLECTING", + "reducer_type": "recovery", "selected_continent": "Testcontinent", "countries": [ { @@ -39,10 +40,10 @@ } ], "authentication_providers": { - "http://localhost:8086/": {}, - "http://localhost:8087/": {}, - "http://localhost:8088/": {}, - "http://localhost:8089/": {} + "http://localhost:8086/": { "status" : "not-contacted" }, + "http://localhost:8087/": { "status" : "not-contacted" }, + "http://localhost:8088/": { "status" : "not-contacted" }, + "http://localhost:8089/": { "status" : "not-contacted" } }, "selected_country": "xx", "currencies": [ "TESTKUDOS" ], diff --git a/src/cli/resources/03-backup.json b/src/cli/resources/03-backup.json index 4dd5368..15627a9 100644 --- a/src/cli/resources/03-backup.json +++ b/src/cli/resources/03-backup.json @@ -2,44 +2,27 @@ "continents": [ "Europe", "North America", - "Testcontinent" + "Demoworld" ], "backup_state": "AUTHENTICATIONS_EDITING", - "selected_continent": "Testcontinent", + "reducer_type": "backup", + "selected_continent": "Demoworld", "countries": [ { "code": "xx", "name": "Testland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, + "continent": "Demoworld", "name_i18n": { "de_DE": "Testlandt", "de_CH": "Testlandi", "fr": "Testpais", "en": "Testland" - }, - "currency": "TESTKUDOS" - }, - { - "code": "xy", - "name": "Demoland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, - "name_i18n": { - "de_DE": "Demolandt", - "de_CH": "Demolandi", - "fr": "Demopais", - "en": "Demoland" - }, - "currency": "KUDOS" + } } ], "authentication_providers": { "http://localhost:8086/": { + "status" : "ok", "methods": [ { "type": "question", @@ -52,13 +35,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "provider_salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", "http_status": 200 }, "http://localhost:8087/": { + "status" : "ok", "methods": [ { "type": "question", @@ -71,13 +54,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "provider_salt": "D378FWXHJB8JHPQFQRZGGV9PWG", "http_status": 200 }, "http://localhost:8088/": { + "status" : "ok", "methods": [ { "type": "question", @@ -90,13 +73,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "provider_salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", "http_status": 200 }, "http://localhost:8089/": { + "status" : "ok", "methods": [ { "type": "question", @@ -109,15 +92,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "provider_salt": "PN0VJF6KDSBYN40SGRCEXPB07M", "http_status": 200 } }, "selected_country": "xx", - "currencies": ["TESTKUDOS"], "required_attributes": [ { "type": "string", diff --git a/src/cli/resources/04-backup.json b/src/cli/resources/04-backup.json index db51f5a..c7dc6fe 100644 --- a/src/cli/resources/04-backup.json +++ b/src/cli/resources/04-backup.json @@ -2,44 +2,27 @@ "continents": [ "Europe", "North America", - "Testcontinent" + "Demoworld" ], "backup_state": "AUTHENTICATIONS_EDITING", - "selected_continent": "Testcontinent", + "reducer_type": "backup", + "selected_continent": "Demoworld", "countries": [ { "code": "xx", "name": "Testland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, + "continent": "Demoworld", "name_i18n": { "de_DE": "Testlandt", "de_CH": "Testlandi", "fr": "Testpais", "en": "Testland" - }, - "currency": "TESTKUDOS" - }, - { - "code": "xy", - "name": "Demoland", - "continent": "Testcontinent", - "continent_i18n": { - "xx": "Testkontinent" - }, - "name_i18n": { - "de_DE": "Demolandt", - "de_CH": "Demolandi", - "fr": "Demopais", - "en": "Demoland" - }, - "currency": "KUDOS" + } } ], "authentication_providers": { "http://localhost:8086/": { + "status" : "ok", "methods": [ { "type": "question", @@ -52,13 +35,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "provider_salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", "http_status": 200 }, "http://localhost:8087/": { + "status" : "ok", "methods": [ { "type": "question", @@ -71,13 +54,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "provider_salt": "D378FWXHJB8JHPQFQRZGGV9PWG", "http_status": 200 }, "http://localhost:8088/": { + "status" : "ok", "methods": [ { "type": "question", @@ -90,13 +73,13 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "provider_salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", "http_status": 200 }, "http://localhost:8089/": { + "status" : "ok", "methods": [ { "type": "question", @@ -109,10 +92,9 @@ "truth_lifetime": { "d_ms": 63115200000 }, - "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "provider_salt": "PN0VJF6KDSBYN40SGRCEXPB07M", "http_status": 200 } }, diff --git a/src/cli/resources/05-backup.json b/src/cli/resources/05-backup.json index 143d9e3..c8f52f0 100644 --- a/src/cli/resources/05-backup.json +++ b/src/cli/resources/05-backup.json @@ -5,6 +5,7 @@ "Testcontinent" ], "backup_state": "POLICIES_REVIEWING", + "reducer_type": "backup", "selected_continent": "Testcontinent", "countries": [ { @@ -40,6 +41,7 @@ ], "authentication_providers": { "http://localhost:8086/": { + "status" : "ok", "methods": [ { "type": "question", @@ -55,10 +57,11 @@ "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "provider_salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", "http_status": 200 }, "http://localhost:8087/": { + "status" : "ok", "methods": [ { "type": "question", @@ -74,10 +77,11 @@ "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "provider_salt": "D378FWXHJB8JHPQFQRZGGV9PWG", "http_status": 200 }, "http://localhost:8088/": { + "status" : "ok", "methods": [ { "type": "question", @@ -93,10 +97,11 @@ "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "provider_salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", "http_status": 200 }, "http://localhost:8089/": { + "status" : "ok", "methods": [ { "type": "question", @@ -112,7 +117,7 @@ "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "provider_salt": "PN0VJF6KDSBYN40SGRCEXPB07M", "http_status": 200 } }, diff --git a/src/cli/resources/06-backup.json b/src/cli/resources/06-backup.json index 9944a17..04d8b02 100644 --- a/src/cli/resources/06-backup.json +++ b/src/cli/resources/06-backup.json @@ -5,6 +5,7 @@ "Testcontinent" ], "backup_state": "SECRET_EDITING", + "reducer_type": "backup", "selected_continent": "Testcontinent", "countries": [ { @@ -40,6 +41,7 @@ ], "authentication_providers": { "http://localhost:8086/": { + "status" : "ok", "methods": [ { "type": "question", @@ -55,10 +57,11 @@ "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "provider_salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", "http_status": 200 }, "http://localhost:8087/": { + "status" : "ok", "methods": [ { "type": "question", @@ -74,10 +77,11 @@ "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "provider_salt": "D378FWXHJB8JHPQFQRZGGV9PWG", "http_status": 200 }, "http://localhost:8088/": { + "status" : "ok", "methods": [ { "type": "question", @@ -93,10 +97,11 @@ "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "provider_salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", "http_status": 200 }, "http://localhost:8089/": { + "status" : "ok", "methods": [ { "type": "question", @@ -112,7 +117,7 @@ "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "provider_salt": "PN0VJF6KDSBYN40SGRCEXPB07M", "http_status": 200 } }, diff --git a/src/cli/setup.sh b/src/cli/setup.sh new file mode 100755 index 0000000..6d26168 --- /dev/null +++ b/src/cli/setup.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# This file is in the public domain + +# Script to be inlined into the main test scripts. Defines function 'setup()' +# which wraps around 'taler-unified-setup.sh' to launch GNU Taler services. +# Call setup() with the arguments to pass to 'taler-unified-setup'. setup() +# will then launch GNU Taler, wait for the process to be complete before +# returning. The script will also install an exit handler to ensure the GNU +# Taler processes are stopped when the shell exits. + +set -eu + +# Cleanup to run whenever we exit +function exit_cleanup() +{ + if [ ! -z ${SETUP_PID+x} ] + then + echo "Killing taler-unified-setup ($SETUP_PID)" >&2 + kill -TERM "$SETUP_PID" 2> /dev/null || true + wait "$SETUP_PID" 2> /dev/null || true + fi +} + +# Install cleanup handler (except for kill -9) +trap exit_cleanup EXIT + +function setup() +{ + echo "Starting test system ..." >&2 + # Create a named pipe in a temp directory we own. + FIFO_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d fifo-XXXXXX) + FIFO_OUT=$(echo "$FIFO_DIR/out") + mkfifo "$FIFO_OUT" + # Open pipe as FD 3 (RW) and FD 4 (RO) + exec 3<> "$FIFO_OUT" 4< "$FIFO_OUT" + rm -rf "$FIFO_DIR" + # We require '-W' for our termination logic to work. + taler-unified-setup.sh -W "$@" >&3 & + SETUP_PID=$! + # Close FD3 + exec 3>&- + sed -u '/<<READY>>/ q' <&4 + # Close FD4 + exec 4>&- + echo "Test system ready" >&2 +} + +# Exit, with status code "skip" (no 'real' failure) +function exit_fail() { + echo "$@" >&2 + exit 1 +} + +# Exit, with status code "skip" (no 'real' failure) +function exit_skip() { + echo "SKIPPING: $1" + exit 77 +} + +function get_payto_uri() { + export LIBEUFIN_SANDBOX_USERNAME="$1" + export LIBEUFIN_SANDBOX_PASSWORD="$2" + export LIBEUFIN_SANDBOX_URL="http://localhost:18082" + libeufin-cli sandbox demobank info --bank-account "$1" | jq --raw-output '.paytoUri' +} + +function get_bankaccount_transactions() { + export LIBEUFIN_SANDBOX_USERNAME=$1 + export LIBEUFIN_SANDBOX_PASSWORD=$2 + export LIBEUFIN_SANDBOX_URL="http://localhost:18082" + libeufin-cli sandbox demobank list-transactions --bank-account $1 +} diff --git a/src/cli/test_anastasis_reducer_1.conf b/src/cli/test_anastasis_reducer_1.conf index 3a05690..2a3a0e4 100644 --- a/src/cli/test_anastasis_reducer_1.conf +++ b/src/cli/test_anastasis_reducer_1.conf @@ -3,7 +3,7 @@ [anastasis] PORT = 8086 -SERVER_SALT = AUfO1KGOKYIFlFQg +PROVIDER_SALT = AUfO1KGOKYIFlFQg BUSINESS_NAME = "Data loss #1 Inc." [stasis-postgres] diff --git a/src/cli/test_anastasis_reducer_2.conf b/src/cli/test_anastasis_reducer_2.conf index 4eef5f0..71b133f 100644 --- a/src/cli/test_anastasis_reducer_2.conf +++ b/src/cli/test_anastasis_reducer_2.conf @@ -3,7 +3,7 @@ [anastasis] PORT = 8087 -SERVER_SALT = BUfO1KGOKYIFlFQg +PROVIDER_SALT = BUfO1KGOKYIFlFQg BUSINESS_NAME = "Data loss #2 Inc." [stasis-postgres] diff --git a/src/cli/test_anastasis_reducer_3.conf b/src/cli/test_anastasis_reducer_3.conf index 08f4700..47233ff 100644 --- a/src/cli/test_anastasis_reducer_3.conf +++ b/src/cli/test_anastasis_reducer_3.conf @@ -3,7 +3,7 @@ [anastasis] PORT = 8088 -SERVER_SALT = CUfO1KGOKYIFlFQg +PROVIDER_SALT = CUfO1KGOKYIFlFQg BUSINESS_NAME = "Data loss #3 Inc." [stasis-postgres] diff --git a/src/cli/test_anastasis_reducer_4.conf b/src/cli/test_anastasis_reducer_4.conf index dee90e3..f515a78 100644 --- a/src/cli/test_anastasis_reducer_4.conf +++ b/src/cli/test_anastasis_reducer_4.conf @@ -3,7 +3,7 @@ [anastasis] PORT = 8089 -SERVER_SALT = DUfO1KGOKYIFlFQg +PROVIDER_SALT = DUfO1KGOKYIFlFQg BUSINESS_NAME = "Data loss #4 Inc." [stasis-postgres] diff --git a/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh b/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh index b8662e8..8a04b0a 100755 --- a/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh +++ b/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh @@ -126,16 +126,6 @@ fi echo "OK" -echo -n "Test user attributes collection in a recovery state ..." -anastasis-reducer -a \ - '{"identity_attributes": { - "full_name": "Max Musterman", - "sq_number": "4", - "birthdate": "2000-01-01"}}' \ - enter_user_attributes resources/02-recovery.json $TFILE 2> /dev/null && exit_fail "Expected recovery to fail due to lacking policy data" - -echo "OK" - rm -f $TFILE exit 0 diff --git a/src/cli/test_anastasis_reducer_done_authentication.sh b/src/cli/test_anastasis_reducer_done_authentication.sh index 87c738c..545c733 100755 --- a/src/cli/test_anastasis_reducer_done_authentication.sh +++ b/src/cli/test_anastasis_reducer_done_authentication.sh @@ -46,9 +46,9 @@ echo " OK" echo -n "Test done authentication (next) ..." -anastasis-reducer next resources/04-backup.json $TFILE +anastasis-reducer next resources/04-backup.json "$TFILE" -STATE=`jq -r -e .backup_state < $TFILE` +STATE=$(jq -r -e .backup_state < "$TFILE") if test "$STATE" != "POLICIES_REVIEWING" then exit_fail "Expected new state to be AUTHENTICATIONS_EDITING, got $STATE" diff --git a/src/cli/test_anastasis_reducer_enter_secret.sh b/src/cli/test_anastasis_reducer_enter_secret.sh index 8005f08..3b25537 100755 --- a/src/cli/test_anastasis_reducer_enter_secret.sh +++ b/src/cli/test_anastasis_reducer_enter_secret.sh @@ -1,6 +1,7 @@ #!/bin/bash # This file is in the public domain. +# shellcheck disable=SC2317 ## Coloring style Text shell script COLOR='\033[0;35m' NOCOLOR='\033[0m' @@ -9,46 +10,22 @@ NORM="$(tput sgr0)" set -eu -# Exit, with status code "skip" (no 'real' failure) -function exit_skip() { - echo " SKIP: $1" - exit 77 -} - -# Exit, with error message (hard failure) -function exit_fail() { - echo " FAIL: $1" - exit 1 -} - -# Cleanup to run whenever we exit -function cleanup() -{ - for n in `jobs -p` - do - kill $n 2> /dev/null || true - done - rm -rf $CONF $WALLET_DB $TFILE $UFILE $TMP_DIR - wait -} - -CONF_1="test_anastasis_reducer_1.conf" -CONF_2="test_anastasis_reducer_2.conf" -CONF_3="test_anastasis_reducer_3.conf" -CONF_4="test_anastasis_reducer_4.conf" - -# Exchange configuration file will be edited, so we create one -# from the template. -CONF=`mktemp test_reducerXXXXXX.conf` -cp test_reducer.conf $CONF - -TMP_DIR=`mktemp -d keys-tmp-XXXXXX` -WALLET_DB=`mktemp test_reducer_walletXXXXXX.json` -TFILE=`mktemp test_reducer_statePPXXXXXX` -UFILE=`mktemp test_reducer_stateBFXXXXXX` - -# Install cleanup handler (except for kill -9) -trap cleanup EXIT +# Replace with 0 for nexus... +USE_FAKEBANK=1 +if [ 1 = "$USE_FAKEBANK" ] +then + ACCOUNT="exchange-account-2" + WIRE_METHOD="x-taler-bank" + BANK_FLAGS="-f -d $WIRE_METHOD -u $ACCOUNT" + BANK_URL="http://localhost:18082/" + MERCHANT_PAYTO="payto://x-taler-bank/localhost/anastasis?receiver-name=anastasis" +else + ACCOUNT="exchange-account-1" + WIRE_METHOD="iban" + BANK_FLAGS="-ns -d $WIRE_METHOD -u $ACCOUNT" + BANK_URL="http://localhost:18082/" + MERCHANT_PAYTO="payto://iban/SANDBOXX/DE648226?receiver-name=anastasis" +fi # Check we can actually run echo -n "Testing for jq" @@ -63,9 +40,6 @@ taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required" taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required" echo " FOUND" -echo -n "Testing for taler-bank-manage" -taler-bank-manage --help >/dev/null </dev/null || exit_skip " MISSING" -echo " FOUND" echo -n "Testing for taler-wallet-cli" taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING" echo " FOUND" @@ -74,87 +48,65 @@ echo -n "Testing for anastasis-httpd" anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING" echo " FOUND" -echo -n "Initialize anastasis database ..." -# Name of the Postgres database we will use for the script. -# Will be dropped, do NOT use anything that might be used -# elsewhere -TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` -TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` -TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` -TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` - -dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1" -anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log -dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2" -anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log -dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3" -anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log -dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4" -anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log +. setup.sh +# Launch exchange, merchant and bank. +# shellcheck disable=SC2086 +setup -c "test_reducer.conf" \ + -aemw \ + $BANK_FLAGS -echo " OK" -echo -n "Generating Taler auditor, exchange and merchant configurations ..." - -DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME` -rm -rf $DATA_DIR - -# obtain key configuration data -MASTER_PRIV_FILE=`taler-config -f -c $CONF -s "EXCHANGE-OFFLINE" -o "MASTER_PRIV_FILE"` -MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE` -mkdir -p $MASTER_PRIV_DIR -gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null 2> /dev/null -MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE` -EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL` -MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT` -MERCHANT_URL=http://localhost:${MERCHANT_PORT}/ -BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT` -BANK_URL=http://localhost:${BANK_PORT}/ -AUDITOR_URL=http://localhost:8083/ -AUDITOR_PRIV_FILE=`taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE` -AUDITOR_PRIV_DIR=`dirname $AUDITOR_PRIV_FILE` -mkdir -p $AUDITOR_PRIV_DIR -gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null 2> /dev/null -AUDITOR_PUB=`gnunet-ecc -p $AUDITOR_PRIV_FILE` - -# patch configuration -TALER_DB=talercheck -taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB -taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB -taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB -taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TALER_DB -taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB -taler-config -c $CONF -s bank -o database -V postgres:///$TALER_DB -taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/" -taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/" +# Cleanup to run whenever we exit +function cleanup() +{ + exit_cleanup + for n in $(jobs -p) + do + kill "$n" 2> /dev/null || true + done + rm -rf "$CONF" "$WALLET_DB" "$TFILE" "$UFILE" "$TMP_DIR" + wait +} -echo " OK" +CONF_1="test_anastasis_reducer_1.conf" +CONF_2="test_anastasis_reducer_2.conf" +CONF_3="test_anastasis_reducer_3.conf" +CONF_4="test_anastasis_reducer_4.conf" -echo -n "Setting up exchange ..." +# Exchange configuration file will be edited, so we create one +# from the template. +CONF="test_reducer.conf.edited" -# reset database -dropdb $TALER_DB >/dev/null 2>/dev/null || true -createdb $TALER_DB || exit_skip "Could not create database $TALER_DB" -taler-exchange-dbinit -c $CONF -taler-merchant-dbinit -c $CONF -taler-auditor-dbinit -c $CONF -taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL +TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d keys-tmp-XXXXXX) +WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_walletXXXXXX.json) +TFILE=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_statePPXXXXXX) +UFILE=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_stateBFXXXXXX) -echo " OK" +# Install cleanup handler (except for kill -9) +trap cleanup EXIT -# Launch services -echo -n "Launching taler services ..." -taler-bank-manage-testing $CONF postgres:///$TALER_DB serve > taler-bank.log 2> taler-bank.err & -taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log & -taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log & -taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log & -taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log & -taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log & -taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log & + +echo -n "Initialize anastasis databases ..." +# Name of the Postgres database we will use for the script. +# Will be dropped, do NOT use anything that might be used +# elsewhere +TARGET_DB_1=$(anastasis-config -c "$CONF_1" -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") +TARGET_DB_2=$(anastasis-config -c "$CONF_2" -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") +TARGET_DB_3=$(anastasis-config -c "$CONF_3" -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") +TARGET_DB_4=$(anastasis-config -c "$CONF_4" -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") + +dropdb "$TARGET_DB_1" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_1" || exit_skip "Could not create database $TARGET_DB_1" +anastasis-dbinit -c "$CONF_1" 2> anastasis-dbinit_1.log +dropdb "$TARGET_DB_2" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_2" || exit_skip "Could not create database $TARGET_DB_2" +anastasis-dbinit -c "$CONF_2" 2> anastasis-dbinit_2.log +dropdb "$TARGET_DB_3" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_3" || exit_skip "Could not create database $TARGET_DB_3" +anastasis-dbinit -c "$CONF_3" 2> anastasis-dbinit_3.log +dropdb "$TARGET_DB_4" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_4" || exit_skip "Could not create database $TARGET_DB_4" +anastasis-dbinit -c "$CONF_4" 2> anastasis-dbinit_4.log echo " OK" @@ -165,82 +117,9 @@ $PREFIX anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log & $PREFIX anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log & $PREFIX anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log & -# Wait for bank to be available (usually the slowest) -for n in `seq 1 50` -do - echo -n "." - sleep 0.2 - OK=0 - # bank - wget --tries=1 --timeout=1 http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue - OK=1 - break -done - -if [ 1 != $OK ] -then - exit_skip "Failed to launch services (bank)" -fi - -# Wait for all other taler services to be available -for n in `seq 1 50` -do - echo -n "." - sleep 0.1 - OK=0 - # exchange - wget --tries=1 --timeout=1 http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue - # merchant - wget --tries=1 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue - # auditor - wget --tries=1 --timeout=1 http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue - OK=1 - break -done - -if [ 1 != $OK ] -then - exit_skip "Failed to launch taler services" -fi - -echo "OK" - -echo -n "Setting up keys ..." -taler-exchange-offline -c $CONF \ - download \ - sign \ - enable-account payto://x-taler-bank/localhost/Exchange \ - enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \ - wire-fee now x-taler-bank TESTKUDOS:0.01 TESTKUDOS:0.01 \ - upload &> taler-exchange-offline.log - -echo -n "." - -for n in `seq 1 3` -do - echo -n "." - OK=0 - wget --tries=1 --timeout=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue - OK=1 - break -done - -if [ 1 != $OK ] -then - exit_skip "Failed to setup keys" -fi - -echo " OK" - -echo -n "Setting up auditor signatures ..." -taler-auditor-offline -c $CONF \ - download sign upload &> taler-auditor-offline.log -echo " OK" - echo -n "Waiting for anastasis services ..." - # Wait for anastasis services to be available -for n in `seq 1 50` +for n in $(seq 1 50) do echo -n "." sleep 0.1 @@ -257,7 +136,7 @@ do break done -if [ 1 != $OK ] +if [ 1 != "$OK" ] then exit_skip "Failed to launch anastasis services" fi @@ -266,154 +145,193 @@ echo "OK" echo -n "Configuring merchant instance ..." # Setup merchant -curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:9966/management/instances +curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances + + +curl -H "Content-Type: application/json" -X POST -d '{"payto_uri":"'"$MERCHANT_PAYTO"'"}' http://localhost:9966/private/accounts + echo " DONE" -echo -en $COLOR$BOLD"Test enter secret in a backup state ..."$NORM$NOCOLOR +echo -en "${COLOR}${BOLD}Test enter secret in a backup state ...${NORM}${NOCOLOR}" $PREFIX anastasis-reducer -a \ '{"secret": { "value" : "veryhardtoguesssecret", "mime" : "text/plain" } }' \ - enter_secret resources/06-backup.json $TFILE + enter_secret resources/06-backup.json "$TFILE" -STATE=`jq -r -e .backup_state < $TFILE` -if test "$STATE" != "SECRET_EDITING" +STATE=$(jq -r -e .backup_state < "$TFILE") +if [ "$STATE" != "SECRET_EDITING" ] then - jq -e . $TFILE + jq -e . "$TFILE" exit_fail "Expected new state to be 'SECRET_EDITING', got '$STATE'" fi echo " DONE" -echo -en $COLOR$BOLD"Test expiration change ..."$NORM$NOCOLOR +echo -en "${COLOR}${BOLD}Test expiration change ...${NORM}${NOCOLOR}" -MILLIS=`date '+%s'`000 +SECS=$(date '+%s') # Use 156 days into the future to get 1 year -MILLIS=`expr $MILLIS + 13478400000` +SECS=$(( SECS + 13478400 )) $PREFIX anastasis-reducer -a \ "$(jq -n ' - {"expiration": { "t_ms" : $MSEC } }' \ - --argjson MSEC $MILLIS + {"expiration": { "t_s" : $SEC } }' \ + --argjson SEC "$SECS" )" \ - update_expiration $TFILE $UFILE + update_expiration "$TFILE" "$UFILE" -STATE=`jq -r -e .backup_state < $UFILE` +STATE=$(jq -r -e .backup_state < "$UFILE") if test "$STATE" != "SECRET_EDITING" then - jq -e . $UFILE + jq -e . "$UFILE" exit_fail "Expected new state to be 'SECRET_EDITING', got '$STATE'" fi -FEES=`jq -r -e '.upload_fees[0].fee' < $UFILE` +FEES=$(jq -r -e '.upload_fees[0].fee' < "$UFILE") # 4x 4.99 for annual fees, plus 4x0.01 for truth uploads -if test "$FEES" != "TESTKUDOS:20" +if [ "$FEES" != "TESTKUDOS:20" ] then - jq -e . $TFILE + jq -e . "$UFILE" exit_fail "Expected upload fees to be 'TESTKUDOS:20', got '$FEES'" fi echo " DONE" -echo -en $COLOR$BOLD"Test advance to payment ..."$NORM$NOCOLOR +echo -en "${COLOR}${BOLD}Test advance to payment ...${NORM}${NOCOLOR}" -$PREFIX anastasis-reducer next $UFILE $TFILE +$PREFIX anastasis-reducer next "$UFILE" "$TFILE" -STATE=`jq -r -e .backup_state < $TFILE` -if test "$STATE" != "TRUTHS_PAYING" +STATE=$(jq -r -e .backup_state < "$TFILE") +if [ "$STATE" != "TRUTHS_PAYING" ] then - jq -e . $TFILE + jq -e . "$TFILE" exit_fail "Expected new state to be 'TRUTHS_PAYING', got '$STATE'" fi -TMETHOD=`jq -r -e '.policies[0].methods[0].truth.type' < $TFILE` -if test $TMETHOD != "question" -then - exit_fail "Expected method to be >='question', got $TMETHOD" -fi +# FIXME: this test is specific to how the +# C reducer stores state (redundantly!), should converge eventually! + +#TMETHOD=$(jq -r -e '.policies[0].methods[0].truth.type' < $TFILE) +#if test $TMETHOD != "question" +#then +# exit_fail "Expected method to be >='question', got $TMETHOD" +#fi +# +#echo " OK" + -echo " OK" #Pay -echo -en $COLOR$BOLD"Withdrawing amount to wallet ..."$NORM$NOCOLOR +echo -en "${COLOR}${BOLD}Withdrawing amount to wallet ...${NORM}${NOCOLOR}" + +EXCHANGE_URL="$(taler-config -c "$CONF" -s exchange -o BASE_URL)" -rm $WALLET_DB -taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \ +rm "$WALLET_DB" +taler-wallet-cli \ + --no-throttle \ + --wallet-db="$WALLET_DB" \ + api \ + --expect-success 'withdrawTestBalance' \ "$(jq -n ' { amount: "TESTKUDOS:40", - bankBaseUrl: $BANK_URL, + corebankApiBaseUrl: $BANK_URL, exchangeBaseUrl: $EXCHANGE_URL }' \ - --arg BANK_URL "$BANK_URL" \ - --arg EXCHANGE_URL "$EXCHANGE_URL" - )" 2>wallet.err >wallet.log -taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet.err >wallet.log + --arg BANK_URL "${BANK_URL}" \ + --arg EXCHANGE_URL "${EXCHANGE_URL}" + )" 2>wallet-withdraw.err \ + >wallet-withdraw.log +taler-wallet-cli \ + --no-throttle \ + --wallet-db="$WALLET_DB" \ + run-until-done \ + 2>wallet-withdraw-finish.err \ + >wallet-withdraw-finish.log echo " OK" -echo -en $COLOR$BOLD"Making payments for truth uploads ... "$NORM$NOCOLOR -OBJECT_SIZE=`jq -r -e '.payments | length' < $TFILE` -for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++)) +echo -en "${COLOR}${BOLD}Making payments for truth uploads ... ${NORM}${NOCOLOR}" +OBJECT_SIZE=$(jq -r -e '.payments | length' < "$TFILE") +for ((INDEX=0; INDEX < "$OBJECT_SIZE"; INDEX++)) do - PAY_URI=`jq --argjson INDEX $INDEX -r -e '.payments[$INDEX]' < $TFILE` + PAY_URI=$(jq --argjson INDEX $INDEX -r -e '.payments[$INDEX]' < "$TFILE") # run wallet CLI echo -n "$INDEX" - taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>wallet.err >wallet.log + taler-wallet-cli \ + --no-throttle \ + --wallet-db="$WALLET_DB" \ + handle-uri "${PAY_URI}" \ + -y \ + 2>wallet-pay1.err \ + >wallet-pay1.log echo -n "," done echo " OK" -echo -e $COLOR$BOLD"Running wallet run-pending..."$NORM$NOCOLOR -taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>wallet.err >wallet.log -echo -e $COLOR$BOLD"Payments done"$NORM$NOCOLOR - - -echo -en $COLOR$BOLD"Try to upload again ..."$NORM$NOCOLOR -$PREFIX anastasis-reducer pay $TFILE $UFILE -mv $UFILE $TFILE +echo -e "${COLOR}${BOLD}Running wallet run-until-done...${NORM}${NOCOLOR}" +taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + run-until-done \ + 2>wallet-pay-finish.err \ + >wallet-pay-finish.log +echo -e "${COLOR}${BOLD}Payments done${NORM}${NOCOLOR}" + + +echo -en "${COLOR}${BOLD}Try to upload again ...${NORM}${NOCOLOR}" +$PREFIX anastasis-reducer pay "$TFILE" "$UFILE" +mv "$UFILE" "$TFILE" echo " OK" -STATE=`jq -r -e .backup_state < $TFILE` -if test "$STATE" != "POLICIES_PAYING" +STATE="$(jq -r -e .backup_state < "$TFILE")" +if [ "$STATE" != "POLICIES_PAYING" ] then exit_fail "Expected new state to be 'POLICIES_PAYING', got '$STATE'" fi -export TFILE -export UFILE - -echo -en $COLOR$BOLD"Making payments for policy uploads ... "$NORM$NOCOLOR -OBJECT_SIZE=`jq -r -e '.policy_payment_requests | length' < $TFILE` -for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++)) +echo -en "${COLOR}${BOLD}Making payments for policy uploads ... ${NORM}${NOCOLOR}" +OBJECT_SIZE="$(jq -r -e '.policy_payment_requests | length' < "$TFILE")" +for ((INDEX=0; INDEX < "$OBJECT_SIZE"; INDEX++)) do - PAY_URI=`jq --argjson INDEX $INDEX -r -e '.policy_payment_requests[$INDEX].payto' < $TFILE` + PAY_URI="$(jq --argjson INDEX "$INDEX" -r -e '.policy_payment_requests[$INDEX].payto' < "$TFILE")" # run wallet CLI export PAY_URI echo -n "$INDEX" - taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>wallet.err >wallet.log + taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + handle-uri "$PAY_URI" \ + -y \ + 2>"wallet-pay2-$INDEX.err" \ + >"wallet-pay2-$INDEX.log" echo -n "," done echo " OK" -echo -e $COLOR$BOLD"Running wallet run-pending..."$NORM$NOCOLOR -taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>wallet.err >wallet.log -echo -e $COLOR$BOLD"Payments done"$NORM$NOCOLOR +echo -e "${COLOR}${BOLD}Running wallet run-until-done...${NORM}${NOCOLOR}" +taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + run-until-done \ + 2>wallet-pay2-finish.err \ + >wallet-pay2-finish.log +echo -e "${COLOR}${BOLD}Payments done${NORM}${NOCOLOR}" -echo -en $COLOR$BOLD"Try to upload again ..."$NORM$NOCOLOR -$PREFIX anastasis-reducer pay $TFILE $UFILE +echo -en "${COLOR}${BOLD}Try to upload again ...${NORM}${NOCOLOR}" +$PREFIX anastasis-reducer pay "$TFILE" "$UFILE" echo " OK" echo -n "Final checks ..." -STATE=`jq -r -e .backup_state < $UFILE` -if test "$STATE" != "BACKUP_FINISHED" +STATE=$(jq -r -e .backup_state < "$UFILE") +if [ "$STATE" != "BACKUP_FINISHED" ] then exit_fail "Expected new state to be BACKUP_FINISHED, got $STATE" fi -jq -r -e .core_secret < $UFILE > /dev/null && exit_fail "'core_secret' was not cleared upon success" +jq -r -e .core_secret \ + < "$UFILE" \ + > /dev/null \ + && exit_fail "'core_secret' was not cleared upon success" echo " OK" - exit 0 diff --git a/src/cli/test_anastasis_reducer_free_1.conf b/src/cli/test_anastasis_reducer_free_1.conf new file mode 100644 index 0000000..0e7ad9a --- /dev/null +++ b/src/cli/test_anastasis_reducer_free_1.conf @@ -0,0 +1,10 @@ +# This file is in the public domain. +@INLINE@ test_reducer_free.conf + +[anastasis] +PORT = 8086 +PROVIDER_SALT = AUfO1KGOKYIFlFQg +BUSINESS_NAME = "Data loss #1 Inc." + +[stasis-postgres] +CONFIG = postgres:///anastasischeck1 diff --git a/src/cli/test_anastasis_reducer_free_2.conf b/src/cli/test_anastasis_reducer_free_2.conf new file mode 100644 index 0000000..a628ff4 --- /dev/null +++ b/src/cli/test_anastasis_reducer_free_2.conf @@ -0,0 +1,10 @@ +# This file is in the public domain. +@INLINE@ test_reducer_free.conf + +[anastasis] +PORT = 8087 +PROVIDER_SALT = BUfO1KGOKYIFlFQg +BUSINESS_NAME = "Data loss #2 Inc." + +[stasis-postgres] +CONFIG = postgres:///anastasischeck2 diff --git a/src/cli/test_anastasis_reducer_free_3.conf b/src/cli/test_anastasis_reducer_free_3.conf new file mode 100644 index 0000000..adbe392 --- /dev/null +++ b/src/cli/test_anastasis_reducer_free_3.conf @@ -0,0 +1,10 @@ +# This file is in the public domain. +@INLINE@ test_reducer_free.conf + +[anastasis] +PORT = 8088 +PROVIDER_SALT = CUfO1KGOKYIFlFQg +BUSINESS_NAME = "Data loss #3 Inc." + +[stasis-postgres] +CONFIG = postgres:///anastasischeck3 diff --git a/src/cli/test_anastasis_reducer_free_4.conf b/src/cli/test_anastasis_reducer_free_4.conf new file mode 100644 index 0000000..cd0c701 --- /dev/null +++ b/src/cli/test_anastasis_reducer_free_4.conf @@ -0,0 +1,10 @@ +# This file is in the public domain. +@INLINE@ test_reducer_free.conf + +[anastasis] +PORT = 8089 +PROVIDER_SALT = DUfO1KGOKYIFlFQg +BUSINESS_NAME = "Data loss #4 Inc." + +[stasis-postgres] +CONFIG = postgres:///anastasischeck4 diff --git a/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh b/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh index d65020e..551ab36 100755 --- a/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh +++ b/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh @@ -1,52 +1,26 @@ #!/bin/bash # This file is in the public domain. -set -eu - -# Exit, with status code "skip" (no 'real' failure) -function exit_skip() { - echo " SKIP: $1" - exit 77 -} - -# Exit, with error message (hard failure) -function exit_fail() { - echo " FAIL: $1" - exit 1 -} - -# Cleanup to run whenever we exit -function cleanup() -{ - for n in `jobs -p` - do - kill $n 2> /dev/null || true - done - rm -rf $CONF $WALLET_DB $R1FILE $R2FILE $B1FILE $B2FILE $TMP_DIR - wait -} - +# shellcheck disable=SC2317 -CONF_1="test_anastasis_reducer_1.conf" -CONF_2="test_anastasis_reducer_2.conf" -CONF_3="test_anastasis_reducer_3.conf" -CONF_4="test_anastasis_reducer_4.conf" - - -# Configuration file will be edited, so we create one -# from the template. -CONF=`mktemp test_reducerXXXXXX.conf` -cp test_reducer.conf $CONF - -TMP_DIR=`mktemp -d keys-tmp-XXXXXX` -WALLET_DB=`mktemp test_reducer_walletXXXXXX.json` -B1FILE=`mktemp test_reducer_stateB1XXXXXX` -B2FILE=`mktemp test_reducer_stateB2XXXXXX` -R1FILE=`mktemp test_reducer_stateR1XXXXXX` -R2FILE=`mktemp test_reducer_stateR2XXXXXX` +set -eu -# Install cleanup handler (except for kill -9) -trap cleanup EXIT +# Replace with 0 for nexus... +USE_FAKEBANK=1 +if [ 1 = "$USE_FAKEBANK" ] +then + ACCOUNT="exchange-account-2" + WIRE_METHOD="x-taler-bank" + BANK_FLAGS="-f -d $WIRE_METHOD -u $ACCOUNT" + BANK_URL="http://localhost:18082/" + MERCHANT_PAYTO="payto://x-taler-bank/localhost/anastasis?receiver-name=anastasis" +else + ACCOUNT="exchange-account-1" + WIRE_METHOD="iban" + BANK_FLAGS="-ns -d $WIRE_METHOD -u $ACCOUNT" + BANK_URL="http://localhost:18082/" + MERCHANT_PAYTO="payto://iban/SANDBOXX/DE648226?receiver-name=anastasis" +fi # Check we can actually run echo -n "Testing for jq" @@ -61,9 +35,6 @@ taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required" taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required" echo " FOUND" -echo -n "Testing for taler-bank-manage" -taler-bank-manage --help >/dev/null </dev/null || exit_skip " MISSING" -echo " FOUND" echo -n "Testing for taler-wallet-cli" taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING" echo " FOUND" @@ -72,173 +43,117 @@ echo -n "Testing for anastasis-httpd" anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING" echo " FOUND" -echo -n "Initialize anastasis database ..." -# Name of the Postgres database we will use for the script. -# Will be dropped, do NOT use anything that might be used -# elsewhere -TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` -TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` -TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` -TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` - -dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1" -anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log -dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2" -anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log -dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3" -anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log -dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true -createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4" -anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log +. setup.sh +# Launch exchange, merchant and bank. +# shellcheck disable=SC2086 +setup -c "test_reducer.conf" \ + -aemw \ + $BANK_FLAGS -echo " OK" +# Cleanup to run whenever we exit +function cleanup() +{ + exit_cleanup + for n in $(jobs -p) + do + kill "$n" 2> /dev/null || true + done + rm -rf "$CONF" "$WALLET_DB" "$R1FILE" "$R2FILE" "$B1FILE" "$B2FILE" "$TMP_DIR" + wait +} -echo -n "Generating Taler auditor, exchange and merchant configurations ..." - -DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME` -rm -rf $DATA_DIR - -# obtain key configuration data -MASTER_PRIV_FILE=`taler-config -f -c $CONF -s "EXCHANGE-OFFLINE" -o "MASTER_PRIV_FILE"` -MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE` -mkdir -p $MASTER_PRIV_DIR -gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null 2> /dev/null -MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE` -EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL` -MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT` -MERCHANT_URL=http://localhost:${MERCHANT_PORT}/ -BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT` -BANK_URL=http://localhost:${BANK_PORT}/ -AUDITOR_URL=http://localhost:8083/ -AUDITOR_PRIV_FILE=`taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE` -AUDITOR_PRIV_DIR=`dirname $AUDITOR_PRIV_FILE` -mkdir -p $AUDITOR_PRIV_DIR -gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null 2> /dev/null -AUDITOR_PUB=`gnunet-ecc -p $AUDITOR_PRIV_FILE` - -# patch configuration -TALER_DB=talercheck -taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB -taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB -taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB -taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TALER_DB -taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB -taler-config -c $CONF -s bank -o database -V postgres:///$TALER_DB -taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/" -taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/" +function sync_providers() { + infile=$1 + outfile=$2 + echo "Synchronizing providers" + # Sync with providers (up to 3 providers aren't synced here) + for x in 1 2 3; do + echo "Synchronizing providers (round $x)" + anastasis-reducer sync_providers < "$infile" > "$outfile" 2> /dev/null || true + CODE=$(jq -r -e ".code // 0" < "$outfile") + # ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED + # FIXME: Temporary workaround for C reducer. See #7227. + if [ "$CODE" = "8420" ] + then + # restore previous non-error state + cat "$infile" > "$outfile" + break + fi + # ANASTASIS_REDUCER_ACTION_INVALID + if [ "$CODE" = "8400" ] + then + # restore previous non-error state + cat "$infile" > "$outfile" + break + fi + if [ "$CODE" != "0" ] + then + exit_fail "Expected no error or 8420/8400, got $CODE" + fi + cat "$outfile" > "$infile" + done + echo "Providers synced." +} -echo " OK" -echo -n "Setting up exchange ..." +CONF_1="test_anastasis_reducer_1.conf" +CONF_2="test_anastasis_reducer_2.conf" +CONF_3="test_anastasis_reducer_3.conf" +CONF_4="test_anastasis_reducer_4.conf" -# reset database -dropdb $TALER_DB >/dev/null 2>/dev/null || true -createdb $TALER_DB || exit_skip "Could not create database $TALER_DB" -taler-exchange-dbinit -c $CONF -taler-merchant-dbinit -c $CONF -taler-auditor-dbinit -c $CONF -taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL -echo " OK" +# Configuration file will be edited, so we create one +# from the template. +CONF="$(mktemp -p "${TMPDIR:-/tmp}" test_reducerXXXXXX.conf)" +cp test_reducer.conf "$CONF" -# Launch services -echo -n "Launching taler services ..." -taler-bank-manage-testing $CONF postgres:///$TALER_DB serve > taler-bank.log 2> taler-bank.err & -taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log & -taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log & -taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log & -taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log & -taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log & -taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log & +TMP_DIR=$(mktemp -p "${TMPDIR:-/tmp}" -d keys-tmp-XXXXXX) +WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_walletXXXXXX.json) +B1FILE=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_stateB1XXXXXX) +B2FILE=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_stateB2XXXXXX) +R1FILE=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_stateR1XXXXXX) +R2FILE=$(mktemp -p "${TMPDIR:-/tmp}" test_reducer_stateR2XXXXXX) + +# Install cleanup handler (except for kill -9) +trap cleanup EXIT + +echo -n "Initialize anastasis database ..." +# Name of the Postgres database we will use for the script. +# Will be dropped, do NOT use anything that might be used +# elsewhere +TARGET_DB_1=$(anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") +TARGET_DB_2=$(anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") +TARGET_DB_3=$(anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") +TARGET_DB_4=$(anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") + +dropdb "$TARGET_DB_1" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_1" || exit_skip "Could not create database $TARGET_DB_1" +anastasis-dbinit -c "$CONF_1" 2> anastasis-dbinit_1.log +dropdb "$TARGET_DB_2" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_2" || exit_skip "Could not create database $TARGET_DB_2" +anastasis-dbinit -c "$CONF_2" 2> anastasis-dbinit_2.log +dropdb "$TARGET_DB_3" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_3" || exit_skip "Could not create database $TARGET_DB_3" +anastasis-dbinit -c "$CONF_3" 2> anastasis-dbinit_3.log +dropdb "$TARGET_DB_4" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB_4" || exit_skip "Could not create database $TARGET_DB_4" +anastasis-dbinit -c "$CONF_4" 2> anastasis-dbinit_4.log echo " OK" echo -n "Launching anastasis services ..." PREFIX="" #valgrind -$PREFIX anastasis-httpd -c $CONF_1 2> anastasis-httpd_1.log & -$PREFIX anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log & -$PREFIX anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log & -$PREFIX anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log & - -# Wait for bank to be available (usually the slowest) -for n in `seq 1 50` -do - echo -n "." - sleep 0.2 - OK=0 - # bank - wget --tries=1 --timeout=1 http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue - OK=1 - break -done - -if [ 1 != $OK ] -then - exit_skip "Failed to launch services (bank)" -fi - -# Wait for all other taler services to be available -for n in `seq 1 50` -do - echo -n "." - sleep 0.1 - OK=0 - # exchange - wget --tries=1 --timeout=1 http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue - # merchant - wget --tries=1 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue - # auditor - wget --tries=1 --timeout=1 http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue - OK=1 - break -done - -if [ 1 != $OK ] -then - exit_skip "Failed to launch taler services" -fi +$PREFIX anastasis-httpd -c "$CONF_1" 2> anastasis-httpd_1.log & +$PREFIX anastasis-httpd -c "$CONF_2" 2> anastasis-httpd_2.log & +$PREFIX anastasis-httpd -c "$CONF_3" 2> anastasis-httpd_3.log & +$PREFIX anastasis-httpd -c "$CONF_4" 2> anastasis-httpd_4.log & echo "OK" -echo -n "Setting up keys ..." -taler-exchange-offline -c $CONF \ - download \ - sign \ - enable-account payto://x-taler-bank/localhost/Exchange \ - enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \ - wire-fee now x-taler-bank TESTKUDOS:0.01 TESTKUDOS:0.01 \ - upload &> taler-exchange-offline.log - -echo -n "." - -for n in `seq 1 3` -do - echo -n "." - OK=0 - wget --tries=1 --timeout=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue - OK=1 - break -done - -if [ 1 != $OK ] -then - exit_skip "Failed to setup keys" -fi - -echo " OK" - -echo -n "Setting up auditor signatures ..." -taler-auditor-offline -c $CONF \ - download sign upload &> taler-auditor-offline.log -echo " OK" - echo -n "Waiting for anastasis services ..." # Wait for anastasis services to be available -for n in `seq 1 50` +for n in $(seq 1 50) do echo -n "." sleep 0.1 @@ -255,7 +170,7 @@ do break done -if [ 1 != $OK ] +if [ 1 != "$OK" ] then exit_skip "Failed to launch anastasis services" fi @@ -264,29 +179,32 @@ echo "OK" echo -n "Configuring merchant instance ..." # Setup merchant -curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:9966/management/instances +curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances +curl -H "Content-Type: application/json" -X POST -d '{"payto_uri":"'"$MERCHANT_PAYTO"'"}' http://localhost:9966/private/accounts echo " DONE" echo -n "Running backup logic ...," -anastasis-reducer -b > $B1FILE +anastasis-reducer -b > "$B1FILE" echo -n "." anastasis-reducer -a \ - '{"continent": "Testcontinent"}' \ - select_continent < $B1FILE > $B2FILE + '{"continent": "Demoworld"}' \ + select_continent < "$B1FILE" > "$B2FILE" echo -n "." anastasis-reducer -a \ - '{"country_code": "xx", - "currencies":["TESTKUDOS"]}' \ - select_country < $B2FILE > $B1FILE + '{"country_code": "xx"}' \ + select_country < "$B2FILE" > "$B1FILE" echo -n "." anastasis-reducer -a \ '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01"}}' \ - enter_user_attributes < $B1FILE > $B2FILE + enter_user_attributes < "$B1FILE" > "$B2FILE" +cat "$B2FILE" > "$B1FILE" +echo -n "," +sync_providers "$B1FILE" "$B2FILE" echo -n "," # "91GPWWR" encodes "Hans" anastasis-reducer -a \ @@ -295,7 +213,7 @@ anastasis-reducer -a \ "instructions": "What is your name?", "challenge": "91GPWWR" } }' \ - add_authentication < $B2FILE > $B1FILE + add_authentication < "$B2FILE" > "$B1FILE" echo -n "." # "64S36" encodes "123" anastasis-reducer -a \ @@ -304,7 +222,7 @@ anastasis-reducer -a \ "instructions": "How old are you?", "challenge": "64S36" } }' \ - add_authentication < $B1FILE > $B2FILE + add_authentication < "$B1FILE" > "$B2FILE" echo -n "." # "9NGQ4WR" encodes "Mars" anastasis-reducer -a \ @@ -313,149 +231,177 @@ anastasis-reducer -a \ "instructions": "Where do you live?", "challenge": "9NGQ4WR" } }' \ - add_authentication < $B2FILE > $B1FILE + add_authentication < "$B2FILE" > "$B1FILE" echo -n "." # Finished adding authentication methods anastasis-reducer \ - next < $B1FILE > $B2FILE + next < "$B1FILE" > "$B2FILE" echo -n "," # Finished policy review anastasis-reducer \ - next < $B2FILE > $B1FILE + next < "$B2FILE" > "$B1FILE" echo -n "." # Note: 'secret' must here be a Crockford base32-encoded value anastasis-reducer -a \ '{"secret": { "value" : "VERYHARDT0GVESSSECRET", "mime" : "text/plain" }}' \ - enter_secret < $B1FILE > $B2FILE -mv $B2FILE $B1FILE -anastasis-reducer next $B1FILE $B2FILE + enter_secret < "$B1FILE" > "$B2FILE" +mv "$B2FILE" "$B1FILE" +anastasis-reducer next "$B1FILE" "$B2FILE" echo " OK" echo -n "Preparing wallet" -rm $WALLET_DB -taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \ + +EXCHANGE_URL="$(taler-config -c "$CONF" -s exchange -o BASE_URL)" + +rm -f "$WALLET_DB" +taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" api --expect-success 'withdrawTestBalance' \ "$(jq -n ' { amount: "TESTKUDOS:100", - bankBaseUrl: $BANK_URL, + corebankApiBaseUrl: $BANK_URL, exchangeBaseUrl: $EXCHANGE_URL }' \ - --arg BANK_URL "$BANK_URL" \ + --arg BANK_URL "${BANK_URL}" \ --arg EXCHANGE_URL "$EXCHANGE_URL" - )" 2> /dev/null >/dev/null -taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>/dev/null >/dev/null + )" 2> wallet-withdraw.err > wallet-withdraw.out +taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + run-until-done \ + 2>wallet-withdraw-finish.err \ + >wallet-withdraw-finish.out echo " OK" echo -en "Making payments for truth uploads ... " -OBJECT_SIZE=`jq -r -e '.payments | length' < $B2FILE` -for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++)) +OBJECT_SIZE=$(jq -r -e '.payments | length' < "$B2FILE") +for ((INDEX=0; INDEX < "$OBJECT_SIZE"; INDEX++)) do - PAY_URI=`jq --argjson INDEX $INDEX -r -e '.payments[$INDEX]' < $B2FILE` + PAY_URI=$(jq --argjson INDEX $INDEX -r -e '.payments[$INDEX]' < "$B2FILE") # run wallet CLI echo -n "$INDEX" - taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>/dev/null >/dev/null + taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + handle-uri "$PAY_URI" \ + -y \ + 2>"wallet-pay-truth-$INDEX.err" \ + >"wallet-pay-truth-$INDEX.out" echo -n ", " done echo "OK" -echo -e "Running wallet run-pending..." -taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>/dev/null >/dev/null +echo -e "Running wallet run-until-done..." +taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + run-until-done \ + 2>"wallet-pay-truth-finish-$INDEX.err" \ + >"wallet-pay-truth-finish-$INDEX.out" echo -e "Payments done" -export B2FILE -export B1FILE - echo -en "Try to upload again ..." -$PREFIX anastasis-reducer pay $B2FILE $B1FILE -mv $B1FILE $B2FILE +$PREFIX anastasis-reducer pay "$B2FILE" "$B1FILE" +mv "$B1FILE" "$B2FILE" echo " OK" echo -en "Making payments for policy uploads ... " -OBJECT_SIZE=`jq -r -e '.policy_payment_requests | length' < $B2FILE` -for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++)) +OBJECT_SIZE=$(jq -r -e '.policy_payment_requests | length' < "$B2FILE") +for ((INDEX=0; INDEX < "$OBJECT_SIZE"; INDEX++)) do - PAY_URI=`jq --argjson INDEX $INDEX -r -e '.policy_payment_requests[$INDEX].payto' < $B2FILE` + PAY_URI=$(jq --argjson INDEX $INDEX -r -e '.policy_payment_requests[$INDEX].payto' < "$B2FILE") # run wallet CLI echo -n "$INDEX" - taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>/dev/null >/dev/null + taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + handle-uri "$PAY_URI" \ + -y \ + 2>"wallet-pay-policy-$INDEX.err" \ + >"wallet-pay-policy-$INDEX.out" echo -n ", " done echo " OK" -echo -en "Running wallet run-pending..." -taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>/dev/null >/dev/null +echo -en "Running wallet run-until-done..." +taler-wallet-cli \ + --wallet-db="$WALLET_DB" \ + run-until-done \ + 2>wallet-pay-policy-finish.err \ + >wallet-pay-policy-finish.out echo -e " payments DONE" echo -en "Try to upload again ..." anastasis-reducer \ - pay < $B2FILE > $B1FILE + pay < "$B2FILE" > "$B1FILE" echo " OK: Backup finished" echo -n "Final backup checks ..." -STATE=`jq -r -e .backup_state < $B1FILE` -if test "$STATE" != "BACKUP_FINISHED" +STATE=$(jq -r -e .backup_state < "$B1FILE") +if [ "$STATE" != "BACKUP_FINISHED" ] then exit_fail "Expected new state to be 'BACKUP_FINISHED', got '$STATE'" fi -jq -r -e .core_secret < $B1FILE > /dev/null && exit_fail "'core_secret' was not cleared upon success" +jq -r -e .core_secret < "$B1FILE" > /dev/null && exit_fail "'core_secret' was not cleared upon success" echo " OK" echo -n "Running recovery basic logic ..." -anastasis-reducer -r > $R1FILE +anastasis-reducer -r > "$R1FILE" anastasis-reducer -a \ - '{"continent": "Testcontinent"}' \ - select_continent < $R1FILE > $R2FILE + '{"continent": "Demoworld"}' \ + select_continent < "$R1FILE" > "$R2FILE" anastasis-reducer -a \ - '{"country_code": "xx", - "currencies":["TESTKUDOS"]}' \ - select_country < $R2FILE > $R1FILE -anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < $R1FILE > $R2FILE + '{"country_code": "xx"}' \ + select_country < "$R2FILE" > "$R1FILE" +anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < "$R1FILE" > "$R2FILE" -STATE=`jq -r -e .recovery_state < $R2FILE` -if test "$STATE" != "SECRET_SELECTING" +STATE=$(jq -r -e .recovery_state < "$R2FILE") +if [ "$STATE" != "SECRET_SELECTING" ] then exit_fail "Expected new state to be 'SECRET_SELECTING', got '$STATE'" fi echo " OK" -echo -n "Selecting default secret" -mv $R2FILE $R1FILE -anastasis-reducer next < $R1FILE > $R2FILE +echo -n "Adding provider (to ensure it is loaded)" +anastasis-reducer -a '{"provider_url" : "http://localhost:8086/" }' add_provider < "$R2FILE" > "$R1FILE" +echo " OK" + +echo -n "Selecting secret to recover" +anastasis-reducer -a '{"attribute_mask": 0, "providers" : [ { "version": 1, "url" : "http://localhost:8086/" } ] }' select_version < "$R1FILE" > "$R2FILE" -STATE=`jq -r -e .recovery_state < $R2FILE` -if test "$STATE" != "CHALLENGE_SELECTING" +STATE=$(jq -r -e .recovery_state < "$R2FILE") +if [ "$STATE" != "CHALLENGE_SELECTING" ] then exit_fail "Expected new state to be 'CHALLENGE_SELECTING', got '$STATE'" fi echo " OK" +cat "$R2FILE" > "$R1FILE" + +sync_providers "$R1FILE" "$R2FILE" + echo -n "Running challenge logic ..." -UUID0=`jq -r -e .recovery_information.challenges[0].uuid < $R2FILE` -UUID1=`jq -r -e .recovery_information.challenges[1].uuid < $R2FILE` -UUID2=`jq -r -e .recovery_information.challenges[2].uuid < $R2FILE` -UUID0Q=`jq -r -e .recovery_information.challenges[0].instructions < $R2FILE` -UUID1Q=`jq -r -e .recovery_information.challenges[1].instructions < $R2FILE` -UUID2Q=`jq -r -e .recovery_information.challenges[2].instructions < $R2FILE` +UUID0=$(jq -r -e .recovery_information.challenges[0].uuid < "$R2FILE") +UUID1=$(jq -r -e .recovery_information.challenges[1].uuid < "$R2FILE") +UUID2=$(jq -r -e .recovery_information.challenges[2].uuid < "$R2FILE") +#UUID0Q=$(jq -r -e .recovery_information.challenges[0].instructions < "$R2FILE") +UUID1Q=$(jq -r -e .recovery_information.challenges[1].instructions < "$R2FILE") +UUID2Q=$(jq -r -e .recovery_information.challenges[2].instructions < "$R2FILE") -if test "$UUID2Q" = 'How old are you?' +if [ "$UUID2Q" = 'How old are you?' ] then AGE_UUID=$UUID2 -elif test "$UUID1Q" = 'How old are you?' +elif [ "$UUID1Q" = 'How old are you?' ] then AGE_UUID=$UUID1 else AGE_UUID=$UUID0 fi -if test "$UUID2Q" = 'What is your name?' +if [ "$UUID2Q" = 'What is your name?' ] then NAME_UUID=$UUID2 -elif test "$UUID1Q" = 'What is your name?' +elif [ "$UUID1Q" = 'What is your name?' ] then NAME_UUID=$UUID1 else @@ -469,10 +415,10 @@ anastasis-reducer -a \ }' \ --arg UUID "$NAME_UUID" )" \ - select_challenge < $R2FILE > $R1FILE + select_challenge < "$R2FILE" > "$R1FILE" anastasis-reducer -a '{"answer": "Hans"}' \ - solve_challenge < $R1FILE > $R2FILE + solve_challenge < "$R1FILE" > "$R2FILE" anastasis-reducer -a \ "$(jq -n ' @@ -481,34 +427,34 @@ anastasis-reducer -a \ }' \ --arg UUID "$AGE_UUID" )" \ - select_challenge < $R2FILE > $R1FILE + select_challenge < "$R2FILE" > "$R1FILE" anastasis-reducer -a '{"answer": "123"}' \ - solve_challenge < $R1FILE > $R2FILE + solve_challenge < "$R1FILE" > "$R2FILE" echo " OK" echo -n "Checking recovered secret ..." # finally: check here that we recovered the secret... -STATE=`jq -r -e .recovery_state < $R2FILE` -if test "$STATE" != "RECOVERY_FINISHED" +STATE=$(jq -r -e .recovery_state < "$R2FILE") +if [ "$STATE" != "RECOVERY_FINISHED" ] then - jq -e . $R2FILE + jq -e . "$R2FILE" exit_fail "Expected new state to be 'RECOVERY_FINISHED', got '$STATE'" fi -SECRET=`jq -r -e .core_secret.value < $R2FILE` -if test "$SECRET" != "VERYHARDT0GVESSSECRET" +SECRET=$(jq -r -e .core_secret.value < "$R2FILE") +if [ "$SECRET" != "VERYHARDT0GVESSSECRET" ] then - jq -e . $R2FILE + jq -e . "$R2FILE" exit_fail "Expected recovered secret to be 'VERYHARDT0GVESSSECRET', got '$SECRET'" fi -MIME=`jq -r -e .core_secret.mime < $R2FILE` -if test "$MIME" != "text/plain" +MIME=$(jq -r -e .core_secret.mime < "$R2FILE") +if [ "$MIME" != "text/plain" ] then - jq -e . $R2FILE + jq -e . "$R2FILE" exit_fail "Expected recovered mime to be 'text/plain', got '$MIME'" fi diff --git a/src/cli/test_anastasis_reducer_recovery_hanging.sh b/src/cli/test_anastasis_reducer_recovery_hanging.sh new file mode 100755 index 0000000..f67b850 --- /dev/null +++ b/src/cli/test_anastasis_reducer_recovery_hanging.sh @@ -0,0 +1,376 @@ +#!/bin/bash +# This file is in the public domain. +# Runs tests with a 'hanging' Anastasis provider. + +set -eu +#set -x + +# Exit, with status code "skip" (no 'real' failure) +function exit_skip() { + echo " SKIP: $1" + exit 77 +} + +# Exit, with error message (hard failure) +function exit_fail() { + echo " FAIL: $1" + exit 1 +} + +# Cleanup to run whenever we exit +function cleanup() +{ + for n in `jobs -p` + do + kill -SIGCONT $n # in case suspended... + kill $n 2> /dev/null || true + done + rm -rf $CONF $R1FILE $R2FILE $B1FILE $B2FILE $TMP_DIR + wait +} + +function sync_providers() { + infile=$1 + outfile=$2 + echo "Synchronizing providers" + # Sync with providers (up to 3 providers aren't synced here) + for x in 1 2 3; do + echo "Synchronizing providers (round $x)" + #anastasis-reducer sync_providers < $infile > $outfile 2> /dev/null || true + anastasis-reducer sync_providers < $infile > $outfile || true + CODE=$(jq -r -e ".code // 0" < $outfile) + # ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED + # FIXME: Temporary workaround for C reducer. See #7227. + if test "$CODE" = "8420"; then + # restore previous non-error state + cat $infile > $outfile + break + fi + # ANASTASIS_REDUCER_ACTION_INVALID + if test "$CODE" = "8400"; then + # restore previous non-error state + cat $infile > $outfile + break + fi + if test "$CODE" != "0"; then + exit_fail "Expected no error or 8420/8400, got $CODE" + fi + cat $outfile > $infile + done + echo "Providers synced." +} + + +CONF_1="test_anastasis_reducer_free_1.conf" +CONF_2="test_anastasis_reducer_free_2.conf" +CONF_3="test_anastasis_reducer_free_3.conf" +CONF_4="test_anastasis_reducer_free_4.conf" + + +# Configuration file will be edited, so we create one +# from the template. +CONF=`mktemp test_reducerXXXXXX.conf` +cp test_reducer.conf $CONF + +TMP_DIR=`mktemp -d keys-tmp-XXXXXX` +B1FILE=`mktemp test_reducer_stateB1XXXXXX` +B2FILE=`mktemp test_reducer_stateB2XXXXXX` +R1FILE=`mktemp test_reducer_stateR1XXXXXX` +R2FILE=`mktemp test_reducer_stateR2XXXXXX` +export B1FILE +export B2FILE +export R1FILE +export R2FILE + +# Install cleanup handler (except for kill -9) +trap cleanup EXIT + +# Check we can actually run +echo -n "Testing for jq" +jq -h > /dev/null || exit_skip "jq required" +echo " FOUND" +echo -n "Testing for timeout" +timeout --help > /dev/null || exit_skip "timeout required" +echo " FOUND" +echo -n "Testing for anastasis-reducer ..." +anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required" +echo " FOUND" + +echo -n "Testing for anastasis-httpd" +anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING" +echo " FOUND" + +echo -n "Initialize anastasis database ..." +# Name of the Postgres database we will use for the script. +# Will be dropped, do NOT use anything that might be used +# elsewhere +TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` +TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` +TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` +TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` + +dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1" +anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log +dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2" +anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log +dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3" +anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log +dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4" +anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log + +echo " OK" + +echo -n "Launching anastasis services ..." +PREFIX="" #valgrind +$PREFIX anastasis-httpd -c $CONF_1 2> anastasis-httpd_1.log & +PPID_1=$! +$PREFIX anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log & +PPID_2=$! +$PREFIX anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log & +PPID_3=$! +$PREFIX anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log & +PPID_4=$! +export PPID_1 +export PPID_2 +export PPID_3 +export PPID_4 + +echo -n "Waiting for anastasis services ..." + +# Wait for anastasis services to be available +for n in `seq 1 50` +do + echo -n "." + sleep 0.1 + OK=0 + # anastasis_01 + wget --tries=1 --timeout=1 http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue + # anastasis_02 + wget --tries=1 --timeout=1 http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue + # anastasis_03 + wget --tries=1 --timeout=1 http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue + # anastasis_04 + wget --tries=1 --timeout=1 http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue + OK=1 + break +done + +if [ 1 != $OK ] +then + exit_skip "Failed to launch anastasis services" +fi +echo "OK" + +echo -n "Running backup logic ...," +anastasis-reducer -b > $B1FILE +echo -n "." +anastasis-reducer -a \ + '{"continent": "Demoworld"}' \ + select_continent < $B1FILE > $B2FILE +echo -n "." +anastasis-reducer -a \ + '{"country_code": "xx"}' \ + select_country < $B2FILE > $B1FILE +echo -n "." + +kill -SIGSTOP $PPID_4 +START=`date '+%s'` +timeout 10 anastasis-reducer -L DEBUG -a \ + '{"identity_attributes": { + "full_name": "Max Musterman", + "sq_number": "4", + "birthdate": "2000-01-01"}}' \ + enter_user_attributes < $B1FILE > $B2FILE || true +END=`date '+%s'` +DELTA=`expr $END - $START` +kill -SIGCONT $PPID_4 + +if test $DELTA -ge 5 +then + exit_fail "Reducer hangs on suspended provider in 'enter_user_attributes'" +fi + +cat $B2FILE > $B1FILE +echo -n "," +sync_providers $B1FILE $B2FILE +echo -n "," +# "91GPWWR" encodes "Hans" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "What is your name?", + "challenge": "91GPWWR" + } }' \ + add_authentication < $B2FILE > $B1FILE +echo -n "." +# "64S36" encodes "123" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "How old are you?", + "challenge": "64S36" + } }' \ + add_authentication < $B1FILE > $B2FILE +echo -n "." +# "9NGQ4WR" encodes "Mars" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "Where do you live?", + "challenge": "9NGQ4WR" + } }' \ + add_authentication < $B2FILE > $B1FILE +echo -n "." +# Finished adding authentication methods +anastasis-reducer \ + next < $B1FILE > $B2FILE + + +echo -n "," +# Finished policy review +anastasis-reducer \ + next < $B2FILE > $B1FILE +echo -n "." + +# Note: 'secret' must here be a Crockford base32-encoded value +anastasis-reducer -a \ + '{"secret": { "value" : "VERYHARDT0GVESSSECRET", "mime" : "text/plain" }}' \ + enter_secret < $B1FILE > $B2FILE +anastasis-reducer next $B2FILE $B1FILE +echo " OK" + +echo -n "Final backup checks ..." +STATE=`jq -r -e .backup_state < $B1FILE` +if test "$STATE" != "BACKUP_FINISHED" +then + exit_fail "Expected new state to be 'BACKUP_FINISHED', got '$STATE'" +fi + +jq -r -e .core_secret < $B1FILE > /dev/null && exit_fail "'core_secret' was not cleared upon success" + +echo " OK" + + +echo -n "Running recovery basic logic ..." +anastasis-reducer -r > $R1FILE +anastasis-reducer -a \ + '{"continent": "Demoworld"}' \ + select_continent < $R1FILE > $R2FILE +anastasis-reducer -a \ + '{"country_code": "xx" }' \ + select_country < $R2FILE > $R1FILE +anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < $R1FILE > $R2FILE + + +STATE=`jq -r -e .recovery_state < $R2FILE` +if test "$STATE" != "SECRET_SELECTING" +then + exit_fail "Expected new state to be 'SECRET_SELECTING', got '$STATE'" +fi +echo " OK" + +echo -n "Adding provider (to ensure it is loaded)" +anastasis-reducer -a '{"provider_url" : "http://localhost:8086/" }' add_provider < $R2FILE > $R1FILE +echo " OK" + +echo -n "Selecting secret to recover" +anastasis-reducer -a '{"attribute_mask": 0, "providers" : [ { "version": 1, "url" : "http://localhost:8086/" } ] }' select_version < $R1FILE > $R2FILE + +STATE=`jq -r -e .recovery_state < $R2FILE` +if test "$STATE" != "CHALLENGE_SELECTING" +then + exit_fail "Expected new state to be 'CHALLENGE_SELECTING', got '$STATE'" +fi +echo " OK" + +cat $R2FILE > $R1FILE +sync_providers $R1FILE $R2FILE + +echo -n "Running challenge logic ..." + +cat $R2FILE | jq . + +UUID0=`jq -r -e .recovery_information.challenges[0].uuid < $R2FILE` +UUID1=`jq -r -e .recovery_information.challenges[1].uuid < $R2FILE` +UUID2=`jq -r -e .recovery_information.challenges[2].uuid < $R2FILE` +UUID0Q=`jq -r -e .recovery_information.challenges[0].instructions < $R2FILE` +UUID1Q=`jq -r -e .recovery_information.challenges[1].instructions < $R2FILE` +UUID2Q=`jq -r -e .recovery_information.challenges[2].instructions < $R2FILE` + +if test "$UUID2Q" = 'How old are you?' +then + AGE_UUID=$UUID2 +elif test "$UUID1Q" = 'How old are you?' +then + AGE_UUID=$UUID1 +else + AGE_UUID=$UUID0 +fi + +if test "$UUID2Q" = 'What is your name?' +then + NAME_UUID=$UUID2 +elif test "$UUID1Q" = 'What is your name?' +then + NAME_UUID=$UUID1 +else + NAME_UUID=$UUID0 +fi + +anastasis-reducer -a \ + "$(jq -n ' + { + uuid: $UUID + }' \ + --arg UUID "$NAME_UUID" + )" \ + select_challenge < $R2FILE > $R1FILE + +anastasis-reducer -a '{"answer": "Hans"}' \ + solve_challenge < $R1FILE > $R2FILE + +anastasis-reducer -a \ + "$(jq -n ' + { + uuid: $UUID + }' \ + --arg UUID "$AGE_UUID" + )" \ + select_challenge < $R2FILE > $R1FILE + +anastasis-reducer -a '{"answer": "123"}' \ + solve_challenge < $R1FILE > $R2FILE + +echo " OK" + +echo -n "Checking recovered secret ..." +# finally: check here that we recovered the secret... + +STATE=`jq -r -e .recovery_state < $R2FILE` +if test "$STATE" != "RECOVERY_FINISHED" +then + jq -e . $R2FILE + exit_fail "Expected new state to be 'RECOVERY_FINISHED', got '$STATE'" +fi + +SECRET=`jq -r -e .core_secret.value < $R2FILE` +if test "$SECRET" != "VERYHARDT0GVESSSECRET" +then + jq -e . $R2FILE + exit_fail "Expected recovered secret to be 'VERYHARDT0GVESSSECRET', got '$SECRET'" +fi + +MIME=`jq -r -e .core_secret.mime < $R2FILE` +if test "$MIME" != "text/plain" +then + jq -e . $R2FILE + exit_fail "Expected recovered mime to be 'text/plain', got '$MIME'" +fi + +echo " OK" + +exit 0 diff --git a/src/cli/test_anastasis_reducer_recovery_no_pay.sh b/src/cli/test_anastasis_reducer_recovery_no_pay.sh new file mode 100755 index 0000000..42f5b0c --- /dev/null +++ b/src/cli/test_anastasis_reducer_recovery_no_pay.sh @@ -0,0 +1,351 @@ +#!/bin/bash +# This file is in the public domain. + +set -eu +set -x + +# Exit, with status code "skip" (no 'real' failure) +function exit_skip() { + echo " SKIP: $1" + exit 77 +} + +# Exit, with error message (hard failure) +function exit_fail() { + echo " FAIL: $1" + exit 1 +} + +# Cleanup to run whenever we exit +function cleanup() +{ + for n in `jobs -p` + do + kill $n 2> /dev/null || true + done + rm -rf $CONF $R1FILE $R2FILE $B1FILE $B2FILE $TMP_DIR + wait +} + +function sync_providers() { + infile=$1 + outfile=$2 + echo "Synchronizing providers" + # Sync with providers (up to 3 providers aren't synced here) + for x in 1 2 3; do + echo "Synchronizing providers (round $x)" + #anastasis-reducer sync_providers < $infile > $outfile 2> /dev/null || true + anastasis-reducer sync_providers < $infile > $outfile || true + CODE=$(jq -r -e ".code // 0" < $outfile) + # ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED + # FIXME: Temporary workaround for C reducer. See #7227. + if test "$CODE" = "8420"; then + # restore previous non-error state + cat $infile > $outfile + break + fi + # ANASTASIS_REDUCER_ACTION_INVALID + if test "$CODE" = "8400"; then + # restore previous non-error state + cat $infile > $outfile + break + fi + if test "$CODE" != "0"; then + exit_fail "Expected no error or 8420/8400, got $CODE" + fi + cat $outfile > $infile + done + echo "Providers synced." +} + + +CONF_1="test_anastasis_reducer_free_1.conf" +CONF_2="test_anastasis_reducer_free_2.conf" +CONF_3="test_anastasis_reducer_free_3.conf" +CONF_4="test_anastasis_reducer_free_4.conf" + + +# Configuration file will be edited, so we create one +# from the template. +CONF=$(mktemp test_reducerXXXXXX.conf) +cp test_reducer.conf "$CONF" + +TMP_DIR=`mktemp -d keys-tmp-XXXXXX` +B1FILE=`mktemp test_reducer_stateB1XXXXXX` +B2FILE=`mktemp test_reducer_stateB2XXXXXX` +R1FILE=`mktemp test_reducer_stateR1XXXXXX` +R2FILE=`mktemp test_reducer_stateR2XXXXXX` +export B1FILE +export B2FILE +export R1FILE +export R2FILE + +# Install cleanup handler (except for kill -9) +trap cleanup EXIT + +# Check we can actually run +echo -n "Testing for jq" +jq -h > /dev/null || exit_skip "jq required" +echo " FOUND" +echo -n "Testing for anastasis-reducer ..." +anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required" +echo " FOUND" + +echo -n "Testing for anastasis-httpd" +anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING" +echo " FOUND" + +echo -n "Initialize anastasis database ..." +# Name of the Postgres database we will use for the script. +# Will be dropped, do NOT use anything that might be used +# elsewhere +TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` +TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` +TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` +TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` + +dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1" +anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log +dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2" +anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log +dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3" +anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log +dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true +createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4" +anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log + +echo " OK" + +echo -n "Launching anastasis services ..." +PREFIX="" #valgrind +$PREFIX anastasis-httpd -L DEBUG -c $CONF_1 2> anastasis-httpd_1.log & +$PREFIX anastasis-httpd -L DEBUG -c $CONF_2 2> anastasis-httpd_2.log & +$PREFIX anastasis-httpd -L DEBUG -c $CONF_3 2> anastasis-httpd_3.log & +$PREFIX anastasis-httpd -L DEBUG -c $CONF_4 2> anastasis-httpd_4.log & + +echo -n "Waiting for anastasis services ..." + +# Wait for anastasis services to be available +for n in `seq 1 50` +do + echo -n "." + sleep 0.1 + OK=0 + # anastasis_01 + wget --tries=1 --timeout=1 http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue + # anastasis_02 + wget --tries=1 --timeout=1 http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue + # anastasis_03 + wget --tries=1 --timeout=1 http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue + # anastasis_04 + wget --tries=1 --timeout=1 http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue + OK=1 + break +done + +if [ 1 != $OK ] +then + exit_skip "Failed to launch anastasis services" +fi +echo "OK" + +echo -n "Running backup logic ...," +anastasis-reducer -b > $B1FILE +echo -n "." +anastasis-reducer -a \ + '{"continent": "Demoworld"}' \ + select_continent < $B1FILE > $B2FILE +echo -n "." +anastasis-reducer -a \ + '{"country_code": "xx"}' \ + select_country < $B2FILE > $B1FILE +echo -n "." +anastasis-reducer -a \ + '{"identity_attributes": { + "full_name": "Max Musterman", + "sq_number": "4", + "birthdate": "2000-01-01"}}' \ + enter_user_attributes < $B1FILE > $B2FILE +cat $B2FILE > $B1FILE +echo -n "," +sync_providers $B1FILE $B2FILE +echo -n "," +# "91GPWWR" encodes "Hans" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "What is your name?", + "challenge": "91GPWWR" + } }' \ + add_authentication < $B2FILE > $B1FILE +echo -n "." +# "64S36" encodes "123" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "How old are you?", + "challenge": "64S36" + } }' \ + add_authentication < $B1FILE > $B2FILE +echo -n "." +# "9NGQ4WR" encodes "Mars" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "Where do you live?", + "challenge": "9NGQ4WR" + } }' \ + add_authentication < $B2FILE > $B1FILE +echo -n "." +# Finished adding authentication methods +anastasis-reducer \ + next < $B1FILE > $B2FILE + + +echo -n "," +# Finished policy review +anastasis-reducer \ + next < $B2FILE > $B1FILE +echo -n "." + +# Note: 'secret' must here be a Crockford base32-encoded value +anastasis-reducer -a \ + '{"secret": { "value" : "VERYHARDT0GVESSSECRET", "mime" : "text/plain" }}' \ + enter_secret < $B1FILE > $B2FILE +anastasis-reducer next $B2FILE $B1FILE +echo " OK" + +echo -n "Final backup checks ..." +STATE=`jq -r -e .backup_state < $B1FILE` +if test "$STATE" != "BACKUP_FINISHED" +then + exit_fail "Expected new state to be 'BACKUP_FINISHED', got '$STATE'" +fi + +jq -r -e .core_secret < $B1FILE > /dev/null && exit_fail "'core_secret' was not cleared upon success" + +echo " OK" + + +echo -n "Running recovery basic logic ..." +anastasis-reducer -r > $R1FILE +anastasis-reducer -a \ + '{"continent": "Demoworld"}' \ + select_continent < $R1FILE > $R2FILE +anastasis-reducer -a \ + '{"country_code": "xx" }' \ + select_country < $R2FILE > $R1FILE +anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < $R1FILE > $R2FILE + + +STATE=`jq -r -e .recovery_state < $R2FILE` +if test "$STATE" != "SECRET_SELECTING" +then + exit_fail "Expected new state to be 'SECRET_SELECTING', got '$STATE'" +fi +echo " OK" + +echo -n "Adding provider (to ensure it is loaded)" +anastasis-reducer -a '{"provider_url" : "http://localhost:8086/" }' add_provider < $R2FILE > $R1FILE +echo " OK" + +echo -n "Selecting secret to recover" +anastasis-reducer -a '{"attribute_mask": 0, "providers" : [ { "version": 1, "url" : "http://localhost:8086/" } ] }' select_version < $R1FILE > $R2FILE + +STATE=`jq -r -e .recovery_state < $R2FILE` +if test "$STATE" != "CHALLENGE_SELECTING" +then + exit_fail "Expected new state to be 'CHALLENGE_SELECTING', got '$STATE'" +fi +echo " OK" + +cat $R2FILE > $R1FILE +sync_providers $R1FILE $R2FILE + +echo -n "Running challenge logic ..." + +cat $R2FILE | jq . + +UUID0=`jq -r -e .recovery_information.challenges[0].uuid < $R2FILE` +UUID1=`jq -r -e .recovery_information.challenges[1].uuid < $R2FILE` +UUID2=`jq -r -e .recovery_information.challenges[2].uuid < $R2FILE` +UUID0Q=`jq -r -e .recovery_information.challenges[0].instructions < $R2FILE` +UUID1Q=`jq -r -e .recovery_information.challenges[1].instructions < $R2FILE` +UUID2Q=`jq -r -e .recovery_information.challenges[2].instructions < $R2FILE` + +if test "$UUID2Q" = 'How old are you?' +then + AGE_UUID=$UUID2 +elif test "$UUID1Q" = 'How old are you?' +then + AGE_UUID=$UUID1 +else + AGE_UUID=$UUID0 +fi + +if test "$UUID2Q" = 'What is your name?' +then + NAME_UUID=$UUID2 +elif test "$UUID1Q" = 'What is your name?' +then + NAME_UUID=$UUID1 +else + NAME_UUID=$UUID0 +fi + +anastasis-reducer -a \ + "$(jq -n ' + { + uuid: $UUID + }' \ + --arg UUID "$NAME_UUID" + )" \ + select_challenge < $R2FILE > $R1FILE + +anastasis-reducer -a '{"answer": "Hans"}' \ + solve_challenge < $R1FILE > $R2FILE + +anastasis-reducer -a \ + "$(jq -n ' + { + uuid: $UUID + }' \ + --arg UUID "$AGE_UUID" + )" \ + select_challenge < $R2FILE > $R1FILE + +anastasis-reducer -a '{"answer": "123"}' \ + solve_challenge < $R1FILE > $R2FILE + +echo " OK" + +echo -n "Checking recovered secret ..." +# finally: check here that we recovered the secret... + +STATE=`jq -r -e .recovery_state < $R2FILE` +if test "$STATE" != "RECOVERY_FINISHED" +then + jq -e . $R2FILE + exit_fail "Expected new state to be 'RECOVERY_FINISHED', got '$STATE'" +fi + +SECRET=`jq -r -e .core_secret.value < $R2FILE` +if test "$SECRET" != "VERYHARDT0GVESSSECRET" +then + jq -e . $R2FILE + exit_fail "Expected recovered secret to be 'VERYHARDT0GVESSSECRET', got '$SECRET'" +fi + +MIME=`jq -r -e .core_secret.mime < $R2FILE` +if test "$MIME" != "text/plain" +then + jq -e . $R2FILE + exit_fail "Expected recovered mime to be 'text/plain', got '$MIME'" +fi + +echo " OK" + +exit 0 diff --git a/src/cli/test_anastasis_reducer_select_continent.sh b/src/cli/test_anastasis_reducer_select_continent.sh index 4cd8a84..bd1ce95 100755 --- a/src/cli/test_anastasis_reducer_select_continent.sh +++ b/src/cli/test_anastasis_reducer_select_continent.sh @@ -41,7 +41,7 @@ echo " FOUND" # Test continent selection in a backup state echo -n "Test continent selection in a backup state ..." -anastasis-reducer -a '{"continent": "Testcontinent"}' select_continent resources/00-backup.json $TFILE +anastasis-reducer -a '{"continent": "Demoworld"}' select_continent resources/00-backup.json $TFILE STATE=`jq -r -e .backup_state < $TFILE` if test "$STATE" != "COUNTRY_SELECTING" @@ -49,9 +49,9 @@ then exit_fail "Expected new state to be COUNTRY_SELECTING, got $STATE" fi SELECTED_CONTINENT=`jq -r -e .selected_continent < $TFILE` -if test "$SELECTED_CONTINENT" != "Testcontinent" +if test "$SELECTED_CONTINENT" != "Demoworld" then - exit_fail "Expected selected continent to be Testcontinent, got $SELECTED_CONTINENT" + exit_fail "Expected selected continent to be Demoworld, got $SELECTED_CONTINENT" fi COUNTRIES=`jq -r -e .countries < $TFILE` if test "$COUNTRIES" == NULL @@ -68,7 +68,7 @@ anastasis-reducer -a '{"continent": "Pangaia"}' select_continent resources/00-re echo " OK" echo -n "Test continent selection in a recovery state ..." -anastasis-reducer -a '{"continent": "Testcontinent"}' select_continent resources/00-recovery.json $TFILE +anastasis-reducer -a '{"continent": "Demoworld"}' select_continent resources/00-recovery.json $TFILE STATE=`jq -r -e .recovery_state < $TFILE` if test "$STATE" != "COUNTRY_SELECTING" @@ -79,12 +79,11 @@ jq -e .countries[0] < $TFILE > /dev/null || exit_fail "Expected new state to inc jq -e .countries[0].code < $TFILE > /dev/null || exit_fail "Expected new state to include countries with code" jq -e .countries[0].continent < $TFILE > /dev/null || exit_fail "Expected new state to include countries with continent" jq -e .countries[0].name < $TFILE > /dev/null || exit_fail "Expected new state to include countries with name" -jq -e .countries[0].currency < $TFILE > /dev/null || exit_fail "Expected new state to include countries with currency" SELECTED_CONTINENT=`jq -r -e .selected_continent < $TFILE` -if test "$SELECTED_CONTINENT" != "Testcontinent" +if test "$SELECTED_CONTINENT" != "Demoworld" then - exit_fail "Expected selected continent to be 'Testcontinent', got $SELECTED_CONTINENT" + exit_fail "Expected selected continent to be 'Demoworld', got $SELECTED_CONTINENT" fi COUNTRIES=`jq -r -e .countries < $TFILE` @@ -96,7 +95,6 @@ jq -e .countries[0] < $TFILE > /dev/null || exit_fail "Expected new state to inc jq -e .countries[0].code < $TFILE > /dev/null || exit_fail "Expected new state to include countries with code" jq -e .countries[0].continent < $TFILE > /dev/null || exit_fail "Expected new state to include countries with continent" jq -e .countries[0].name < $TFILE > /dev/null || exit_fail "Expected new state to include countries with name" -jq -e .countries[0].currency < $TFILE > /dev/null || exit_fail "Expected new state to include countries with currency" echo " OK" diff --git a/src/cli/test_anastasis_reducer_select_country.sh b/src/cli/test_anastasis_reducer_select_country.sh index c02f61f..2e18f44 100755 --- a/src/cli/test_anastasis_reducer_select_country.sh +++ b/src/cli/test_anastasis_reducer_select_country.sh @@ -63,12 +63,11 @@ jq -e .countries[0] < $TFILE > /dev/null || exit_fail "Expected new state to inc jq -e .countries[0].code < $TFILE > /dev/null || exit_fail "Expected new state to include countries with code" jq -e .countries[0].continent < $TFILE > /dev/null || exit_fail "Expected new state to include countries with continent" jq -e .countries[0].name < $TFILE > /dev/null || exit_fail "Expected new state to include countries with name" -jq -e .countries[0].currency < $TFILE > /dev/null || exit_fail "Expected new state to include countries with currency" SELECTED_CONTINENT=`jq -r -e .selected_continent < $TFILE` if test "$SELECTED_CONTINENT" != "Europe" then - exit_fail "Expected selected continent to be 'Testcontinent', got $SELECTED_CONTINENT" + exit_fail "Expected selected continent to be 'Europe', got $SELECTED_CONTINENT" fi echo " OK" @@ -84,8 +83,7 @@ echo " OK" echo -n "Test NX country selection ..." anastasis-reducer -a \ - '{"country_code": "zz", - "currencies": ["EUR" ]}' \ + '{"country_code": "zz"}' \ select_country \ resources/01-backup.json $TFILE 2> /dev/null \ && exit_fail "Expected selection to fail. Check '$TFILE'" @@ -95,8 +93,7 @@ echo " OK" echo -n "Test invalid country selection for continent ..." anastasis-reducer -a \ - '{"country_code": "de", - "currencies":["EUR"]}' \ + '{"country_code": "de"}' \ select_country \ resources/01-backup.json $TFILE 2> /dev/null \ && exit_fail "Expected selection to fail. Check '$TFILE'" @@ -106,8 +103,7 @@ echo " OK" echo -n "Test country selection ..." anastasis-reducer -a \ - '{"country_code": "xx", - "currencies":["TESTKUDOS"]}' \ + '{"country_code": "xx"}' \ select_country resources/01-backup.json $TFILE STATE=`jq -r -e .backup_state < $TFILE` @@ -122,12 +118,6 @@ then exit_fail "Expected selected country to be 'xx', got '$SELECTED_COUNTRY'" fi echo -n "." -SELECTED_CURRENCY=`jq -r -e .currencies[0] < $TFILE` -if test "$SELECTED_CURRENCY" != "TESTKUDOS" -then - exit_fail "Expected selected currency to be 'TESTKUDOS', got '$SELECTED_CURRENCY'" -fi -echo -n "." REQ_ATTRIBUTES=`jq -r -e .required_attributes < $TFILE` if test "$REQ_ATTRIBUTES" == NULL then diff --git a/src/cli/test_free_reducer.conf b/src/cli/test_free_reducer.conf index 74954cd..8308537 100644 --- a/src/cli/test_free_reducer.conf +++ b/src/cli/test_free_reducer.conf @@ -11,7 +11,7 @@ UPLOAD_LIMIT_MB = 1 ANNUAL_POLICY_UPLOAD_LIMIT = 128 INSURANCE = EUR:0 PORT = 8086 -SERVER_SALT = BUfO1KGOKYIFlFQg +PROVIDER_SALT = BUfO1KGOKYIFlFQg BUSINESS_NAME = "Data loss Inc." [stasis] diff --git a/src/cli/test_iban.sh b/src/cli/test_iban.sh index cb31f6a..207d2d5 100755 --- a/src/cli/test_iban.sh +++ b/src/cli/test_iban.sh @@ -1,6 +1,8 @@ #!/bin/bash +# This file is in the public domain. set -eu +#set -x # Exit, with status code "skip" (no 'real' failure) function exit_skip() { @@ -17,38 +19,66 @@ function exit_fail() { # Cleanup to run whenever we exit function cleanup() { - for n in `jobs -p` + for n in $(jobs -p) do - kill $n 2> /dev/null || true + kill "$n" 2> /dev/null || true done - rm -rf $CONF $R1FILE $R2FILE $B1FILE $B2FILE + rm -rf "$CONF" "$R1FILE" "$R2FILE" "$B1FILE" "$B2FILE" wait } # $1=ebics username, $2=ebics partner name, $3=person name, $4=sandbox bank account name, $5=iban function prepare_sandbox_account() { - echo -n "Activating ebics subscriber $1 at the sandbox ..." - libeufin-cli \ - sandbox --sandbox-url=$SANDBOX_URL \ - ebicssubscriber create \ - --host-id=$EBICS_HOST \ - --partner-id=$2 \ - --user-id=$1 + echo -n "Registering $4 to the Sandbox..." + export LIBEUFIN_SANDBOX_USERNAME="$4" + export LIBEUFIN_SANDBOX_PASSWORD=unused + libeufin-cli sandbox --sandbox-url="$SANDBOX_URL" \ + demobank register --name "$3" --iban "$5" echo " OK" - echo -n "Giving a bank account ($4) to $1 ..." - libeufin-cli \ - sandbox --sandbox-url=$SANDBOX_URL \ - ebicsbankaccount create \ - --iban=$5 \ - --bic="BCMAESM1XXX"\ - --person-name="$3" \ - --account-name=$4 \ - --ebics-user-id=$1 \ - --ebics-host-id=$EBICS_HOST \ - --ebics-partner-id=$2 \ - --currency=$CURRENCY + echo -n "Associating a EBICS subscriber to $4..." + export LIBEUFIN_SANDBOX_USERNAME=admin + libeufin-cli sandbox --sandbox-url="$SANDBOX_URL" demobank new-ebicssubscriber \ + --host-id "$EBICS_HOST" \ + --user-id "$1" --partner-id "$2" \ + --bank-account "$4" # that's a username _and_ a bank account name echo " OK" + + unset LIBEUFIN_SANDBOX_USERNAME + unset LIBEUFIN_SANDBOX_PASSWORD +} + +function sync_providers() { + infile="$1" + outfile="$2" + echo "Synchronizing providers" + # Sync with providers (up to 3 providers aren't synced here) + for x in 1 2 3; do + echo "Synchronizing providers (round $x)" + anastasis-reducer sync_providers < "$infile" > "$outfile" 2> /dev/null || true + CODE=$(jq -r -e ".code // 0" < $outfile) + # ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED + # FIXME: Temporary workaround for C reducer. See #7227. + if [ "$CODE" = "8420" ] + then + # restore previous non-error state + cp "$infile" "$outfile" + break + fi + # ANASTASIS_REDUCER_ACTION_INVALID + if [ "$CODE" = "8400" ] + then + # restore previous non-error state + cp "$infile" "$outfile" + break + fi + if [ "$CODE" != "0" ] + then + exit_fail "Expected no error or 8420/8400, got $CODE" + fi + cp "$outfile" "$infile" + done + echo "Providers synced." } @@ -61,13 +91,19 @@ trap cleanup EXIT # to pass through the Nexus+Ebics layer to issue the payment # $1 = amount ($CURRENCY:X.Y), $2 = subject. function wire_transfer_to_anastasis() { + echo -n "Initiating wire transfer ..." libeufin-sandbox make-transaction \ --debit-account=sandbox-account-debit \ - --credit-account=sandbox-account-credit "$1" "$2" - # Sync nexus with sandbox - export LIBEUFIN_NEXUS_USERNAME=$CREDIT_USERNAME - export LIBEUFIN_NEXUS_PASSWORD=$CREDIT_PASSWORD - libeufin-cli accounts fetch-transactions nexus-bankaccount-credit > /dev/null + --credit-account=sandbox-account-credit "$1" "$2" &> libeufin-transfer-initiate.out + echo " OK" + # FIXME-MS: the following command reports that it did not + # sync any transactions, even though presumably we just + # made one in the one above (which succeeded...) + echo -n "Syncing nexus with sandbox ..." + export LIBEUFIN_NEXUS_USERNAME="$CREDIT_USERNAME" + export LIBEUFIN_NEXUS_PASSWORD="$CREDIT_PASSWORD" + libeufin-cli accounts fetch-transactions nexus-bankaccount-credit &> libeufin-transfer-fetch.out + echo " OK" } # $1 = facade base URL. Merely a debug utility. @@ -82,43 +118,33 @@ function prepare_nexus_account() { echo -n "Making bank connection $3 ..." libeufin-cli connections new-ebics-connection \ --ebics-url="${SANDBOX_URL}ebicsweb" \ - --host-id=$EBICS_HOST \ - --partner-id=$2 \ - --ebics-user-id=$1 \ + --host-id="$EBICS_HOST" \ + --partner-id="$2" \ + --ebics-user-id="$1" \ $3 > /dev/null echo " OK" echo -n "Connecting $3 ..." - libeufin-cli connections connect $3 > /dev/null + libeufin-cli connections connect "$3" > /dev/null echo " OK" echo -n "Importing Sandbox bank account ($5) to Nexus ($4) ..." - libeufin-cli connections download-bank-accounts $3 > /dev/null + libeufin-cli connections download-bank-accounts "$3" > /dev/null libeufin-cli connections import-bank-account \ - --offered-account-id=$5 --nexus-bank-account-id=$4 $3 > /dev/null + --offered-account-id="$5" --nexus-bank-account-id="$4" "$3" > /dev/null echo " OK" } -# $1 = facade name, $2 = bank connection to use, $3 = bank account name -# local to Nexus -function prepare_anastasis_facade() { - echo -n "Creating facade ..." - libeufin-cli facades new-anastasis-facade \ - --currency=$CURRENCY \ - --facade-name=$1 \ - $2 $3 - echo " OK" - # No need to setup facade permissions, as the anastasis client - # is superuser at Nexus. -} # Configuration file will be edited, so we create one # from the template. -CONF=`mktemp test_free_reducerXXXXXX.conf` -cp test_free_reducer.conf $CONF +CONF=$(mktemp test_free_reducerXXXXXX.conf) +cp test_free_reducer.conf "$CONF" + -B1FILE=`mktemp test_reducer_stateB1XXXXXX` -B2FILE=`mktemp test_reducer_stateB2XXXXXX` -R1FILE=`mktemp test_reducer_stateR1XXXXXX` -R2FILE=`mktemp test_reducer_stateR2XXXXXX` + +B1FILE=$(mktemp test_reducer_stateB1XXXXXX) +B2FILE=$(mktemp test_reducer_stateB2XXXXXX) +R1FILE=$(mktemp test_reducer_stateR1XXXXXX) +R2FILE=$(mktemp test_reducer_stateR2XXXXXX) export CONF export B2FILE @@ -146,23 +172,40 @@ echo -n "Testing for anastasis-reducer ..." anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required" echo " FOUND" -export LIBEUFIN_NEXUS_DB_CONNECTION="jdbc:sqlite:$(mktemp -u /tmp/nexus-db-XXXXXX.sqlite)" -export LIBEUFIN_SANDBOX_DB_CONNECTION="jdbc:sqlite:$(mktemp -u /tmp/sandbox-db-XXXXXX.sqlite)" +echo -n "Initialize Anastasis database ..." +# Name of the Postgres database we will use for the script. +# Will be dropped, do NOT use anything that might be used +# elsewhere + +TARGET_DB=$(anastasis-config -c "$CONF" -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///") + +dropdb "$TARGET_DB" >/dev/null 2>/dev/null || true +createdb "$TARGET_DB" || exit_skip "Could not create database $TARGET_DB" +anastasis-dbinit -c "$CONF" 2> anastasis-dbinit.log + +echo " OK" + + +export LIBEUFIN_NEXUS_DB_CONNECTION="postgres:///anastasischeck" +export LIBEUFIN_SANDBOX_DB_CONNECTION="postgres:///anastasischeck" NEXUS_URL="http://localhost:5001/" SANDBOX_URL="http://localhost:5000/" echo -n "Starting Nexus ..." libeufin-nexus serve &> nexus.log & nexus_pid=$! -if ! curl -s --retry 5 --retry-connrefused $NEXUS_URL > /dev/null; then +if ! curl -s --retry 5 --retry-connrefused "$NEXUS_URL" > /dev/null; then exit_skip "Could not launch Nexus" fi echo " OK" +echo -n "Configuring Sandbox..." +libeufin-sandbox config default &> sandbox-config.log +echo " OK" echo -n "Starting Sandbox ..." -libeufin-sandbox serve &> sandbox.log & +libeufin-sandbox serve --no-auth &> sandbox-serve.log & sandbox_pid=$! -if ! curl -s --retry 5 --retry-connrefused $SANDBOX_URL > /dev/null; then +if ! curl -s --retry 5 --retry-connrefused "$SANDBOX_URL" > /dev/null; then exit_skip "Could not launch Sandbox" fi echo " OK" @@ -174,11 +217,11 @@ EBICS_HOST="ebicstesthost" export IBAN_CREDIT="DE89370400440532013000" export IBAN_DEBIT="FR1420041010050500013M02606" -echo -n "Preparing Sandbox ..." +echo -n "Preparing Sandbox (creating the EBICS host) ..." libeufin-cli \ - sandbox --sandbox-url=$SANDBOX_URL \ + sandbox --sandbox-url="$SANDBOX_URL" \ ebicshost create \ - --host-id=$EBICS_HOST + --host-id="$EBICS_HOST" echo " OK" PERSON_CREDIT_NAME="Person Credit" @@ -189,41 +232,32 @@ prepare_sandbox_account \ ebicspartnerCredit \ "${PERSON_CREDIT_NAME}" \ sandbox-account-credit \ - $IBAN_CREDIT + "$IBAN_CREDIT" prepare_sandbox_account \ ebicsuserDebit \ ebicspartnerDebit \ "Person Debit" \ sandbox-account-debit \ - $IBAN_DEBIT + "$IBAN_DEBIT" echo "Sandbox preparation done" echo -n "Preparing Nexus ..." -export LIBEUFIN_NEXUS_URL=$NEXUS_URL +export LIBEUFIN_NEXUS_URL="$NEXUS_URL" # Make debit user, will buy Anastasis services. DEBIT_USERNAME=anastasis-debit-user DEBIT_PASSWORD=anastasis-debit-password -libeufin-nexus superuser $DEBIT_USERNAME --password=$DEBIT_PASSWORD +libeufin-nexus superuser "$DEBIT_USERNAME" --password="$DEBIT_PASSWORD" echo " OK" -export LIBEUFIN_NEXUS_USERNAME=$DEBIT_USERNAME -export LIBEUFIN_NEXUS_PASSWORD=$DEBIT_PASSWORD - -# FIXME: this command below likely not needed. Please -# remove, run the test, and commit+push if it still works! -prepare_nexus_account \ - ebicsuserDebit \ - ebicspartnerDebit \ - bankconnection-debit \ - nexus-bankaccount-debit \ - sandbox-account-debit +export LIBEUFIN_NEXUS_USERNAME="$DEBIT_USERNAME" +export LIBEUFIN_NEXUS_PASSWORD="$DEBIT_PASSWORD" # Make credit user, will be Anastasis client. CREDIT_USERNAME=anastasis-credit-user CREDIT_PASSWORD=anastasis-credit-password echo -n "Create credit user (for anastasis) at Nexus ..." -libeufin-nexus superuser $CREDIT_USERNAME --password=$CREDIT_PASSWORD +libeufin-nexus superuser "$CREDIT_USERNAME" --password="$CREDIT_PASSWORD" echo " OK" -export LIBEUFIN_NEXUS_USERNAME=$CREDIT_USERNAME -export LIBEUFIN_NEXUS_PASSWORD=$CREDIT_PASSWORD +export LIBEUFIN_NEXUS_USERNAME="$CREDIT_USERNAME" +export LIBEUFIN_NEXUS_PASSWORD="$CREDIT_PASSWORD" prepare_nexus_account \ ebicsuserCredit \ @@ -234,7 +268,7 @@ prepare_nexus_account \ echo -n "Create facade ..." libeufin-cli facades new-anastasis-facade \ - --currency=$CURRENCY \ + --currency="$CURRENCY" \ --facade-name=facade-credit \ bankconnection-credit nexus-bankaccount-credit echo " OK" @@ -242,41 +276,29 @@ FACADE_URL=$(libeufin-cli facades list | jq .facades[0].baseUrl | tr -d \") ## Reach facade with: $FACADE_URL + $CREDIT_USERNAME + $CREDIT_PASSWORD -echo -n "Initialize Anastasis database ..." -# Name of the Postgres database we will use for the script. -# Will be dropped, do NOT use anything that might be used -# elsewhere - -TARGET_DB=`anastasis-config -c $CONF -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"` - -dropdb $TARGET_DB >/dev/null 2>/dev/null || true -createdb $TARGET_DB || exit_skip "Could not create database $TARGET_DB" -anastasis-dbinit -c $CONF 2> anastasis-dbinit.log - -echo " OK" echo -n "Configuring Anastasis IBAN account ..." -anastasis-config -c $CONF \ +anastasis-config -c "$CONF" \ -s authorization-iban \ -o CREDIT_IBAN \ -V "${IBAN_CREDIT}" -anastasis-config -c $CONF \ +anastasis-config -c "$CONF" \ -s authorization-iban \ -o BUSINESS_NAME \ -V "${PERSON_CREDIT_NAME}" -anastasis-config -c $CONF \ +anastasis-config -c "$CONF" \ -s authorization-iban \ -o WIRE_GATEWAY_URL \ -V "${FACADE_URL}" -anastasis-config -c $CONF \ +anastasis-config -c "$CONF" \ -s authorization-iban \ -o WIRE_GATEWAY_AUTH_METHOD \ -V "basic" -anastasis-config -c $CONF \ +anastasis-config -c "$CONF" \ -s authorization-iban \ -o USERNAME \ -V "${LIBEUFIN_NEXUS_USERNAME}" -anastasis-config -c $CONF \ +anastasis-config -c "$CONF" \ -s authorization-iban \ -o PASSWORD \ -V "${LIBEUFIN_NEXUS_PASSWORD}" @@ -284,12 +306,12 @@ echo " OK" echo -n "Launching Anastasis service ..." PREFIX="" #valgrind -$PREFIX anastasis-httpd -c $CONF -L INFO 2> anastasis-httpd_1.log & +$PREFIX anastasis-httpd -c "$CONF" -L INFO 2> anastasis-httpd_1.log & echo " OK" echo -n "Waiting for Anastasis service ..." # Wait for Anastasis service to be available -for n in `seq 1 50` +for n in $(seq 1 50) do echo -n "." sleep 0.1 @@ -306,16 +328,15 @@ fi echo "OK" echo -n "Running backup logic ...," -anastasis-reducer -b > $B1FILE +anastasis-reducer -b > "$B1FILE" echo -n "." anastasis-reducer -a \ - '{"continent": "Testcontinent"}' \ - select_continent < $B1FILE > $B2FILE + '{"continent": "Demoworld"}' \ + select_continent < "$B1FILE" > "$B2FILE" echo -n "." anastasis-reducer -a \ - '{"country_code": "xx", - "currencies":["TESTKUDOS"]}' \ - select_country < $B2FILE > $B1FILE 2>> test_reducer.err + '{"country_code": "xx" }' \ + select_country < "$B2FILE" > "$B1FILE" 2>> test_reducer.err echo -n "." anastasis-reducer -a \ @@ -323,9 +344,12 @@ anastasis-reducer -a \ "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01"}}' \ - enter_user_attributes < $B1FILE > $B2FILE 2>> test_reducer.err + enter_user_attributes < "$B1FILE" > "$B2FILE" 2>> test_reducer.err echo -n "," -BASEIBAN=`echo -n $IBAN_DEBIT | gnunet-base32` +cat "$B2FILE" > "$B1FILE" +sync_providers "$B1FILE" "$B2FILE" +echo -n "," +BASEIBAN=$(echo -n $IBAN_DEBIT | gnunet-base32) anastasis-reducer -a \ "$(jq -n '{ authentication_method: { type: "iban", @@ -334,67 +358,102 @@ anastasis-reducer -a \ } }' \ --arg CHALLENGE "$BASEIBAN" )" \ - add_authentication < $B2FILE > $B1FILE 2>> test_reducer.err + add_authentication < "$B2FILE" > "$B1FILE" 2>> test_reducer.err echo -n "." + +# "91GPWWR" encodes "Hans" +anastasis-reducer -a \ + '{"authentication_method": { + "type": "question", + "instructions": "What is your name?", + "challenge": "91GPWWR" + } }' \ + add_authentication < "$B1FILE" > "$B2FILE" 2>> test_reducer.err +echo -n "." + +mv "$B2FILE" "$B1FILE" + # Finished adding authentication methods anastasis-reducer \ - next < $B1FILE > $B2FILE 2>> test_reducer.err + next < "$B1FILE" > "$B2FILE" 2>> test_reducer.err echo -n "," # Finished policy review anastasis-reducer \ - next < $B2FILE > $B1FILE 2>> test_reducer.err + next < "$B2FILE" > "$B1FILE" 2>> test_reducer.err echo -n "." # Note: 'secret' must here be a Crockford base32-encoded value anastasis-reducer -a \ '{"secret": { "value" : "VERYHARDT0GVESSSECRET", "mime" : "text/plain" }}' \ - enter_secret < $B1FILE > $B2FILE 2>> test_reducer.err -mv $B2FILE $B1FILE -anastasis-reducer next < $B1FILE > $B2FILE 2>> test_reducer.err + enter_secret < "$B1FILE" > "$B2FILE" 2>> test_reducer.err +mv "$B2FILE" "$B1FILE" +anastasis-reducer next < "$B1FILE" > "$B2FILE" 2>> test_reducer.err echo " OK" echo -n "Final backup checks ..." -STATE=`jq -r -e .backup_state < $B2FILE` -if test "$STATE" != "BACKUP_FINISHED" +STATE=$(jq -r -e .backup_state < "$B2FILE") +if [ "$STATE" != "BACKUP_FINISHED" ] then exit_fail "Expected new state to be 'BACKUP_FINISHED', got '$STATE'" fi -jq -r -e .core_secret < $B2FILE > /dev/null && exit_fail "'core_secret' was not cleared upon success" +jq -r -e .core_secret < "$B2FILE" > /dev/null && exit_fail "'core_secret' was not cleared upon success" echo " OK" echo -n "Running recovery basic logic ..." -anastasis-reducer -r > $R1FILE +anastasis-reducer -r > "$R1FILE" anastasis-reducer -a \ - '{"continent": "Testcontinent"}' \ - select_continent < $R1FILE > $R2FILE + '{"continent": "Demoworld"}' \ + select_continent < "$R1FILE" > "$R2FILE" anastasis-reducer -a \ '{"country_code": "xx", "currencies":["TESTKUDOS"]}' \ - select_country < $R2FILE > $R1FILE 2>> test_reducer.err -anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < $R1FILE > $R2FILE 2>> test_reducer.err + select_country < "$R2FILE" > "$R1FILE" 2>> test_reducer.err +anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < "$R1FILE" > "$R2FILE" 2>> test_reducer.err -STATE=`jq -r -e .recovery_state < $R2FILE` -if test "$STATE" != "SECRET_SELECTING" +STATE=$(jq -r -e .recovery_state < "$R2FILE") +if [ "$STATE" != "SECRET_SELECTING" ] then exit_fail "Expected new state to be 'SECRET_SELECTING', got '$STATE'" fi echo " OK" -echo -n "Selecting default secret" -mv $R2FILE $R1FILE -anastasis-reducer next < $R1FILE > $R2FILE 2>> test_reducer.err +echo -n "Adding provider (to ensure it is loaded)" +anastasis-reducer -a '{"provider_url" : "http://localhost:8086/" }' add_provider < "$R2FILE" > "$R1FILE" +echo " OK" -STATE=`jq -r -e .recovery_state < $R2FILE` -if test "$STATE" != "CHALLENGE_SELECTING" +echo -n "Selecting secret to recover" +anastasis-reducer -a '{"attribute_mask": 0, "providers" : [ { "version": 1, "url" : "http://localhost:8086/" } ] }' \ + select_version < "$R1FILE" > "$R2FILE" 2>> test_reducer.err + +STATE=$(jq -r -e .recovery_state < "$R2FILE") +if [ "$STATE" != "CHALLENGE_SELECTING" ] then exit_fail "Expected new state to be 'CHALLENGE_SELECTING', got '$STATE'" fi echo " OK" +cp "$R2FILE" "$R1FILE" +sync_providers "$R1FILE" "$R2FILE" + echo -n "Running challenge selection logic ..." -NAME_UUID=`jq -r -e .recovery_information.challenges[0].uuid < $R2FILE` +UUID0=$(jq -r -e .recovery_information.challenges[0].uuid < "$R2FILE") +UUID1=$(jq -r -e .recovery_information.challenges[1].uuid < "$R2FILE") +UUID0Q=$(jq -r -e .recovery_information.challenges[0].instructions < "$R2FILE") +UUID1Q=$(jq -r -e .recovery_information.challenges[1].instructions < "$R2FILE") + +if [ "$UUID1Q" = 'What is your name?' ] +then + NAME_UUID=$UUID1 + IBAN_UUID=$UUID0 +else + NAME_UUID=$UUID0 + IBAN_UUID=$UUID1 +fi + +echo "OK" +echo -n "Solving first challenge ..." anastasis-reducer -a \ "$(jq -n ' { @@ -402,64 +461,79 @@ anastasis-reducer -a \ }' \ --arg UUID "$NAME_UUID" )" \ - select_challenge < $R2FILE > $R1FILE 2>> test_reducer.err + select_challenge < "$R2FILE" > "$R1FILE" 2>> test_reducer.err + +anastasis-reducer -a '{"answer": "Hans"}' \ + solve_challenge < "$R1FILE" > "$R2FILE" + +echo "OK" +echo -n "Solving IBAN challenge ..." +anastasis-reducer -a \ + "$(jq -n ' + { + uuid: $UUID + }' \ + --arg UUID "$IBAN_UUID" + )" \ + select_challenge < "$R2FILE" > "$R1FILE" 2>> test_reducer.err echo "OK" -METHOD=`jq -r -e .challenge_feedback.\"$NAME_UUID\".method < $R1FILE` -if test "$METHOD" != "iban" + +METHOD=$(jq -r -e .challenge_feedback.\"$IBAN_UUID\".state < "$R1FILE") +if [ "$METHOD" != "iban-instructions" ] then - exit_fail "Expected method to be 'iban', got ${METHOD}" + exit_fail "Expected method to be 'iban-instructions', got ${METHOD}" fi -ACC=`jq -r -e .challenge_feedback.\"$NAME_UUID\".details.credit_iban < $R1FILE` -if test "$ACC" != ${IBAN_CREDIT} +ACC=$(jq -r -e .challenge_feedback.\"$IBAN_UUID\".target_iban < "$R1FILE") +if [ "$ACC" != "${IBAN_CREDIT}" ] then exit_fail "Expected account to be ${IBAN_CREDIT}, got ${ACC}" fi anastasis-reducer \ - back < $R1FILE > $R2FILE 2>> test_reducer.err + back < "$R1FILE" > "$R2FILE" 2>> test_reducer.err -AMOUNT=`jq -r -e .challenge_feedback.\"$NAME_UUID\".details.challenge_amount < $R1FILE` -SUBJECT=`jq -r -e .challenge_feedback.\"$NAME_UUID\".details.wire_transfer_subject < $R1FILE` +AMOUNT=$(jq -r -e .challenge_feedback.\"$IBAN_UUID\".challenge_amount < "$R1FILE") +SUBJECT=$(jq -r -e .challenge_feedback.\"$IBAN_UUID\".wire_transfer_subject < "$R1FILE") -echo -n "Performing authorization wire transfer ..." +echo -n "Performing authorization wire transfer ${SUBJECT} ..." wire_transfer_to_anastasis "${AMOUNT}" "${SUBJECT}" echo " OK" echo -n "Triggering inbound check ..." -anastasis-helper-authorization-iban -c $CONF -t +anastasis-helper-authorization-iban -c "$CONF" -t -L INFO echo " OK" # Now we should get the secret... echo -n "Polling for recovery ..." -anastasis-reducer poll < $R2FILE > $R1FILE +anastasis-reducer poll -L INFO < "$R2FILE" > "$R1FILE" echo " OK" echo -n "Checking recovered secret ..." # finally: check here that we recovered the secret... -STATE=`jq -r -e .recovery_state < $R1FILE` -if test "$STATE" != "RECOVERY_FINISHED" +STATE=$(jq -r -e .recovery_state < "$R1FILE") +if [ "$STATE" != "RECOVERY_FINISHED" ] then - jq -e . $R1FILE + jq -e . "$R1FILE" exit_fail "Expected new state to be 'RECOVERY_FINISHED', got '$STATE'" fi -SECRET=`jq -r -e .core_secret.value < $R1FILE` -if test "$SECRET" != "VERYHARDT0GVESSSECRET" +SECRET=$(jq -r -e .core_secret.value < "$R1FILE") +if [ "$SECRET" != "VERYHARDT0GVESSSECRET" ] then - jq -e . $R1FILE + jq -e . "$R1FILE" exit_fail "Expected recovered secret to be 'VERYHARDT0GVESSSECRET', got '$SECRET'" fi -MIME=`jq -r -e .core_secret.mime < $R1FILE` -if test "$MIME" != "text/plain" +MIME=$(jq -r -e .core_secret.mime < "$R1FILE") +if [ "$MIME" != "text/plain" ] then - jq -e . $R1FILE + jq -e . "$R1FILE" exit_fail "Expected recovered mime to be 'text/plain', got '$MIME'" fi diff --git a/src/cli/test_reducer.conf b/src/cli/test_reducer.conf index df68b14..4f26a79 100644 --- a/src/cli/test_reducer.conf +++ b/src/cli/test_reducer.conf @@ -26,9 +26,10 @@ COST = TESTKUDOS:0.0 [exchange] +MASTER_PUBLIC_KEY = 3NX5DJDBD8XVGZYHV3PBF8C3Z4GK48XD59YY5GF3CZE8AJM04WSG +AML_THRESHOLD = TESTKUDOS:1000000 MAX_KEYS_CACHING = forever DB = postgres -MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv SERVE = tcp UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http UNIXPATH_MODE = 660 @@ -39,26 +40,40 @@ SIGNKEY_LEGAL_DURATION = 2 years LEGAL_DURATION = 2 years LOOKAHEAD_SIGN = 3 weeks 1 day LOOKAHEAD_PROVIDE = 2 weeks 1 day -KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/ -REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/ TERMS_ETAG = 0 PRIVACY_ETAG = 0 +STEFAN_ABS = "TESTKUDOS:5" + +# Account of the EXCHANGE +[exchange-account-1] +# What is the exchange's bank account (with the "Taler Bank" demo system)? +PAYTO_URI = payto://iban/SANDBOXX/DE989651?receiver-name=Exchange+Company +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES + +[exchange-accountcredentials-1] +WIRE_GATEWAY_URL = http://localhost:18082/accounts/exchange/taler-wire-gateway/ +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = exchange +PASSWORD = x + + +[exchange-account-2] +PAYTO_URI = "payto://x-taler-bank/localhost/exchange?receiver-name=exchange" +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES + +[exchange-accountcredentials-2] +WIRE_GATEWAY_AUTH_METHOD = none +WIRE_GATEWAY_URL = "http://localhost:18082/accounts/exchange/taler-wire-gateway/" [merchant] SERVE = tcp PORT = 9966 UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http UNIXPATH_MODE = 660 -DEFAULT_WIRE_FEE_AMORTIZATION = 1 DB = postgres -WIREFORMAT = default -# Set very low, so we can be sure that the database generated -# will contain wire transfers "ready" for the aggregator. -WIRE_TRANSFER_DELAY = 1 minute -DEFAULT_PAY_DEADLINE = 1 day -DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1 KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv -DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10 # Ensure that merchant reports EVERY deposit confirmation to auditor FORCE_AUDIT = YES @@ -79,30 +94,41 @@ BASE_URL = "http://localhost:8083/" DATABASE = postgres:///taler-auditor-basedb MAX_DEBT = TESTKUDOS:50.0 MAX_DEBT_BANK = TESTKUDOS:100000.0 -HTTP_PORT = 8082 +HTTP_PORT = 18082 SUGGESTED_EXCHANGE = http://localhost:8081/ SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2 ALLOW_REGISTRATIONS = YES SERVE = http +[libeufin-bank] +CURRENCY = TESTKUDOS +WIRE_TYPE = iban +IBAN_PAYTO_BIC = SANDBOXX +DEFAULT_CUSTOMER_DEBT_LIMIT = TESTKUDOS:200 +DEFAULT_ADMIN_DEBT_LIMIT = TESTKUDOS:2000 +REGISTRATION_BONUS_ENABLED = yes +REGISTRATION_BONUS = TESTKUDOS:100 +SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/ +SERVE = tcp +PORT = 18082 + [exchangedb] IDLE_RESERVE_EXPIRATION_TIME = 4 weeks LEGAL_RESERVE_EXPIRATION_TIME = 7 years -[exchange-account-1] -PAYTO_URI = payto://x-taler-bank/localhost/Exchange -enable_debit = yes -enable_credit = yes +[auditordb-postgres] +CONFIG = "postgres:///talercheck" -[exchange-accountcredentials-1] -WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/" -WIRE_GATEWAY_AUTH_METHOD = basic -USERNAME = Exchange -PASSWORD = x +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[merchantdb-postgres] +CONFIG = "postgres:///talercheck" [merchant-exchange-default] EXCHANGE_BASE_URL = http://localhost:8081/ CURRENCY = TESTKUDOS +MASTER_KEY = 3NX5DJDBD8XVGZYHV3PBF8C3Z4GK48XD59YY5GF3CZE8AJM04WSG [payments-generator] currency = TESTKUDOS @@ -123,6 +149,7 @@ fee_deposit = TESTKUDOS:0.01 fee_refresh = TESTKUDOS:0.01 fee_refund = TESTKUDOS:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_ct_10] value = TESTKUDOS:0.10 @@ -134,6 +161,7 @@ fee_deposit = TESTKUDOS:0.01 fee_refresh = TESTKUDOS:0.03 fee_refund = TESTKUDOS:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_1] value = TESTKUDOS:1 @@ -145,6 +173,7 @@ fee_deposit = TESTKUDOS:0.02 fee_refresh = TESTKUDOS:0.03 fee_refund = TESTKUDOS:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_2] value = TESTKUDOS:2 @@ -156,6 +185,7 @@ fee_deposit = TESTKUDOS:0.03 fee_refresh = TESTKUDOS:0.04 fee_refund = TESTKUDOS:0.02 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_4] value = TESTKUDOS:4 @@ -167,6 +197,7 @@ fee_deposit = TESTKUDOS:0.03 fee_refresh = TESTKUDOS:0.04 fee_refund = TESTKUDOS:0.02 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_5] value = TESTKUDOS:5 @@ -178,6 +209,7 @@ fee_deposit = TESTKUDOS:0.01 fee_refresh = TESTKUDOS:0.03 fee_refund = TESTKUDOS:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_8] value = TESTKUDOS:8 @@ -189,6 +221,7 @@ fee_deposit = TESTKUDOS:0.02 fee_refresh = TESTKUDOS:0.03 fee_refund = TESTKUDOS:0.04 rsa_keysize = 1024 +CIPHER = RSA [coin_kudos_10] value = TESTKUDOS:10 @@ -200,3 +233,4 @@ fee_deposit = TESTKUDOS:0.01 fee_refresh = TESTKUDOS:0.03 fee_refund = TESTKUDOS:0.01 rsa_keysize = 1024 +CIPHER = RSA diff --git a/src/cli/test_reducer_free.conf b/src/cli/test_reducer_free.conf new file mode 100644 index 0000000..4e46929 --- /dev/null +++ b/src/cli/test_reducer_free.conf @@ -0,0 +1,210 @@ +# This file is in the public domain. +[PATHS] +TALER_HOME = ${PWD}/test_reducer_home/ +TALER_DATA_HOME = $TALER_HOME/.local/share/taler/ +TALER_CONFIG_HOME = $TALER_HOME/.config/taler/ +TALER_CACHE_HOME = $TALER_HOME/.cache/taler/ +TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/ + +[taler] +CURRENCY = TESTKUDOS +CURRENCY_ROUND_UNIT = TESTKUDOS:0.01 + +[anastasis] +DB = postgres +ANNUAL_FEE = TESTKUDOS:0 +TRUTH_UPLOAD_FEE = TESTKUDOS:0.0 +UPLOAD_LIMIT_MB = 1 +ANNUAL_POLICY_UPLOAD_LIMIT = 128 +INSURANCE = TESTKUDOS:0 + +[anastasis-merchant-backend] +PAYMENT_BACKEND_URL = http://localhost:9966/ + +[authorization-question] +COST = TESTKUDOS:0.0 + + +[exchange] +MAX_KEYS_CACHING = forever +DB = postgres +MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv +SERVE = tcp +UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http +UNIXPATH_MODE = 660 +PORT = 8081 +BASE_URL = http://localhost:8081/ +SIGNKEY_DURATION = 2 weeks +SIGNKEY_LEGAL_DURATION = 2 years +LEGAL_DURATION = 2 years +LOOKAHEAD_SIGN = 3 weeks 1 day +LOOKAHEAD_PROVIDE = 2 weeks 1 day +KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/ +REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/ +TERMS_ETAG = 0 +PRIVACY_ETAG = 0 + +[merchant] +SERVE = tcp +PORT = 9966 +UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http +UNIXPATH_MODE = 660 +DEFAULT_WIRE_FEE_AMORTIZATION = 1 +DB = postgres +WIREFORMAT = default +# Set very low, so we can be sure that the database generated +# will contain wire transfers "ready" for the aggregator. +WIRE_TRANSFER_DELAY = 1 minute +DEFAULT_PAY_DEADLINE = 1 day +DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1 +KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv +DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10 + +# Ensure that merchant reports EVERY deposit confirmation to auditor +FORCE_AUDIT = YES + +[auditor] +DB = postgres +AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv +SERVE = tcp +UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http +UNIXPATH_MODE = 660 +PORT = 8083 +AUDITOR_URL = http://localhost:8083/ +TINY_AMOUNT = TESTKUDOS:0.01 +AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv +BASE_URL = "http://localhost:8083/" + +[bank] +DATABASE = postgres:///taler-auditor-basedb +MAX_DEBT = TESTKUDOS:50.0 +MAX_DEBT_BANK = TESTKUDOS:100000.0 +HTTP_PORT = 8082 +SUGGESTED_EXCHANGE = http://localhost:8081/ +SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2 +ALLOW_REGISTRATIONS = YES +SERVE = http + +[exchangedb] +IDLE_RESERVE_EXPIRATION_TIME = 4 weeks +LEGAL_RESERVE_EXPIRATION_TIME = 7 years + +[exchange-account-1] +PAYTO_URI = payto://x-taler-bank/localhost/Exchange +enable_debit = yes +enable_credit = yes + +[exchange-accountcredentials-1] +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/Exchange/taler-wire-gateway/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + +[merchant-exchange-default] +EXCHANGE_BASE_URL = http://localhost:8081/ +CURRENCY = TESTKUDOS + +[payments-generator] +currency = TESTKUDOS +instance = default +bank = http://localhost:8082/ +merchant = http://localhost:9966/ +exchange_admin = http://localhost:18080/ +exchange-admin = http://localhost:18080/ +exchange = http://localhost:8081/ + +[coin_kudos_ct_1] +value = TESTKUDOS:0.01 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.01 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_ct_10] +value = TESTKUDOS:0.10 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_1] +value = TESTKUDOS:1 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.02 +fee_deposit = TESTKUDOS:0.02 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_2] +value = TESTKUDOS:2 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.03 +fee_deposit = TESTKUDOS:0.03 +fee_refresh = TESTKUDOS:0.04 +fee_refund = TESTKUDOS:0.02 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_4] +value = TESTKUDOS:4 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.03 +fee_deposit = TESTKUDOS:0.03 +fee_refresh = TESTKUDOS:0.04 +fee_refund = TESTKUDOS:0.02 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_5] +value = TESTKUDOS:5 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_8] +value = TESTKUDOS:8 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.05 +fee_deposit = TESTKUDOS:0.02 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.04 +rsa_keysize = 1024 +CIPHER = RSA + +[coin_kudos_10] +value = TESTKUDOS:10 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = TESTKUDOS:0.01 +fee_deposit = TESTKUDOS:0.01 +fee_refresh = TESTKUDOS:0.03 +fee_refund = TESTKUDOS:0.01 +rsa_keysize = 1024 +CIPHER = RSA diff --git a/src/cli/test_reducer_home/.local/share/taler/exchange-offline/master.priv b/src/cli/test_reducer_home/.local/share/taler/exchange-offline/master.priv new file mode 100644 index 0000000..d990a05 --- /dev/null +++ b/src/cli/test_reducer_home/.local/share/taler/exchange-offline/master.priv @@ -0,0 +1 @@ +ý>eÍ”nƒÍ™[˜ùz3‘ÔÜpwTj?cÉn21
\ No newline at end of file diff --git a/src/include/anastasis.h b/src/include/anastasis.h index a950172..ea49ee7 100644 --- a/src/include/anastasis.h +++ b/src/include/anastasis.h @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -97,61 +97,43 @@ ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge); /** * Possible outcomes of trying to start a challenge operation. */ -enum ANASTASIS_ChallengeStatus +enum ANASTASIS_ChallengeStartStatus { /** - * The challenge has been solved. - */ - ANASTASIS_CHALLENGE_STATUS_SOLVED, - - /** - * Instructions for how to solve the challenge are provided. Also - * used if the answer we provided was wrong (or if no answer was - * provided, but one is needed). - */ - ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS, - - /** - * A redirection URL needed to solve the challenge is provided. Also - * used if the answer we provided was wrong (or if no answer was - * provided, but one is needed). + * We encountered an error talking to the Anastasis service. */ - ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION, + ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE, /** * Payment is required before the challenge can be answered. */ - ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED, + ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED, /** - * We encountered an error talking to the Anastasis service. + * The server does not know this truth. */ - ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE, + ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN, /** - * The server does not know this truth. + * A filename with the TAN has been provided. */ - ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN, + ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED, /** - * The rate limit for solving the challenge was exceeded. + * A TAN has been send, address hint is provided. */ - ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, + ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED, /** - * The user did not satisfy the (external) authentication - * challenge in time. The request should be repeated - * later and may then succeed. + * A TAN has been sent before. */ - ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT, + ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT, /** - * Plugin-specific ("external") instructions for how to solve the - * challenge are provided. + * Wire transfer required, banking details provided. */ - ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS - + ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED }; @@ -161,10 +143,21 @@ enum ANASTASIS_ChallengeStatus */ struct ANASTASIS_ChallengeStartResponse { + + /** + * HTTP status returned by the server. + */ + unsigned int http_status; + + /** + * Taler-specific error code. + */ + enum TALER_ErrorCode ec; + /** * What is our status on satisfying this challenge. Determines @e details. */ - enum ANASTASIS_ChallengeStatus cs; + enum ANASTASIS_ChallengeStartStatus cs; /** * Which challenge is this about? @@ -179,50 +172,25 @@ struct ANASTASIS_ChallengeStartResponse /** * Challenge details provided if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED. */ - struct - { - - /** - * Response with server-side instructions for the user. - */ - const void *body; - - /** - * Mime type of the data in @e body. - */ - const char *content_type; - - /** - * Number of bytes in @e body - */ - size_t body_size; - - /** - * HTTP status returned by the server. #MHD_HTTP_ALREADY_REPORTED - * if the server did already send the challenge to the user, - * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing). - */ - unsigned int http_status; - } open_challenge; - + const char *tan_filename; /** - * Response with details if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS. + * Challenge details provided if + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED. */ - const json_t *external_challenge; + const char *tan_address_hint; /** - * Response with URL to redirect the user to, if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION. + * Challenge details provided if + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED. */ - const char *redirect_url; + struct ANASTASIS_WireFundsDetails bank_transfer_required; /** * Response with instructions for how to pay, if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED. + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED. */ struct { @@ -239,26 +207,6 @@ struct ANASTASIS_ChallengeStartResponse } payment_required; - - /** - * Response with details about a server-side failure, if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE. - */ - struct - { - - /** - * HTTP status returned by the server. - */ - unsigned int http_status; - - /** - * Taler-specific error code. - */ - enum TALER_ErrorCode ec; - - } server_failure; - } details; }; @@ -271,7 +219,7 @@ struct ANASTASIS_ChallengeStartResponse * @param csr response details */ typedef void -(*ANASTASIS_AnswerFeedback)( +(*ANASTASIS_ChallengeStartFeedback)( void *cls, const struct ANASTASIS_ChallengeStartResponse *csr); @@ -285,22 +233,144 @@ typedef void * * @param c reference to the escrow challenge which is started * @param psp payment secret, NULL if no payment was yet made - * @param timeout how long to wait for payment - * @param hashed_answer answer to the challenge, NULL if we have none yet * @param af reference to the answerfeedback which is passed back to the user * @param af_cls closure for @a af * @return #GNUNET_OK if the challenge was successfully started */ -int +enum GNUNET_GenericReturnValue ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, - struct GNUNET_TIME_Relative timeout, - const struct GNUNET_HashCode *hashed_answer, - ANASTASIS_AnswerFeedback af, + ANASTASIS_ChallengeStartFeedback af, void *af_cls); /** + * Possible outcomes of trying to start a challenge operation. + */ +enum ANASTASIS_ChallengeAnswerStatus +{ + + /** + * The challenge has been solved. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED, + + /** + * Payment is required before the challenge can be answered. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED, + + /** + * We encountered an error talking to the Anastasis service. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE, + + /** + * The server does not know this truth. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN, + + /** + * The answer was wrong. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER, + + /** + * The rate limit for solving the challenge was exceeded. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED + +}; + + +/** + * Response from an #ANASTASIS_challenge_start() operation. + */ +struct ANASTASIS_ChallengeAnswerResponse +{ + + /** + * HTTP status returned by the server. + */ + unsigned int http_status; + + /** + * Taler-specific error code. + */ + enum TALER_ErrorCode ec; + + /** + * What is our status on satisfying this challenge. Determines @e details. + */ + enum ANASTASIS_ChallengeAnswerStatus cs; + + /** + * Which challenge is this about? + */ + struct ANASTASIS_Challenge *challenge; + + /** + * Details depending on @e cs + */ + union + { + + /** + * Details for #ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED. + */ + struct + { + + /** + * How many requests are allowed at most per @e request_frequency? + */ + uint32_t request_limit; + + /** + * Frequency at which requests are allowed / new challenges are + * created. + */ + struct GNUNET_TIME_Relative request_frequency; + + } rate_limit_exceeded; + + /** + * Response with instructions for how to pay, if + * @e cs is #ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED. + */ + struct + { + + /** + * "taler://pay" URI with details how to pay for the challenge. + */ + const char *taler_pay_uri; + + /** + * Payment secret from @e taler_pay_uri. + */ + struct ANASTASIS_PaymentSecretP payment_secret; + + } payment_required; + + } details; +}; + + +/** + * Defines a callback for the response status for a challenge start + * operation. + * + * @param cls closure + * @param car response details + */ +typedef void +(*ANASTASIS_AnswerFeedback)( + void *cls, + const struct ANASTASIS_ChallengeAnswerResponse *car); + + +/** * Challenge answer for a security question. Is referenced to * a challenge and sends back an AnswerFeedback. Convenience * wrapper around #ANASTASIS_challenge_start that hashes @a answer @@ -310,17 +380,17 @@ ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c, * @param psp information about payment made for the recovery * @param timeout how long to wait for payment * @param answer user input instruction defines which input is needed - * @param af reference to the answerfeedback which is passed back to the user - * @param af_cls closure for @a af + * @param csf function to call with the result + * @param csf_cls closure for @a csf * @return #GNUNET_OK on success */ -int +enum GNUNET_GenericReturnValue ANASTASIS_challenge_answer (struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, struct GNUNET_TIME_Relative timeout, const char *answer, - ANASTASIS_AnswerFeedback af, - void *af_cls); + ANASTASIS_AnswerFeedback csf, + void *csf_cls); /** @@ -337,7 +407,7 @@ ANASTASIS_challenge_answer (struct ANASTASIS_Challenge *c, * @param af_cls closure for @a af * @return #GNUNET_OK on success */ -int +enum GNUNET_GenericReturnValue ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, struct GNUNET_TIME_Relative timeout, @@ -347,6 +417,30 @@ ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c, /** + * User starts a challenge which reponds out of bounds (E-Mail, SMS, + * Postal..) If the challenge is zero cost, the challenge + * instructions will be sent to the client. If the challenge needs + * payment a payment link is sent to the client. After payment the + * challenge start method has to be called again. + * + * @param c reference to the escrow challenge which is started + * @param psp payment secret, NULL if no payment was yet made + * @param timeout how long to wait for payment + * @param hashed_answer answer to the challenge + * @param af reference to the answerfeedback which is passed back to the user + * @param af_cls closure for @a af + * @return #GNUNET_OK if the challenge was successfully started + */ +enum GNUNET_GenericReturnValue +ANASTASIS_challenge_answer3 (struct ANASTASIS_Challenge *c, + const struct ANASTASIS_PaymentSecretP *psp, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_HashCode *hashed_answer, + ANASTASIS_AnswerFeedback af, + void *af_cls); + + +/** * Abort answering challenge. * * @param c reference to the escrow challenge which was started @@ -356,6 +450,67 @@ ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c); /** + * Handle for an operation to get available recovery + * document versions. + */ +struct ANASTASIS_VersionCheck; + + +/** + * Callback which passes back meta data about one of the + * recovery documents available at the provider. + * + * @param cls closure for the callback + * @param version version number of the policy document, + * 0 for the end of the list + * @param server_time time of the backup at the provider + * @param recdoc_id hash of the compressed recovery document, uniquely + * identifies the document; NULL for the end of the list + * @param secret_name name of the secret as chosen by the user, + * or NULL if the user did not provide a name + */ +typedef void +(*ANASTASIS_MetaPolicyCallback)(void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp server_time, + const struct GNUNET_HashCode *recdoc_id, + const char *secret_name); + + +/** + * Obtain an overview of available recovery policies from the + * specified provider. + * + * @param ctx context for making HTTP requests + * @param id_data contains the users identity, (user account on providers) + * @param version defines the version which will be downloaded, 0 for latest version + * @param anastasis_provider_url provider url + * @param provider_salt the server salt + * @param mpc function called with the available versions + * @param mpc_cls closure for @a mpc callback + * @return recovery operation handle + */ +struct ANASTASIS_VersionCheck * +ANASTASIS_recovery_get_versions ( + struct GNUNET_CURL_Context *ctx, + const json_t *id_data, + unsigned int max_version, + const char *anastasis_provider_url, + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt, + ANASTASIS_MetaPolicyCallback mpc, + void *mpc_cls); + + +/** + * Cancel version check operation. + * + * @param vc operation to cancel + */ +void +ANASTASIS_recovery_get_versions_cancel (struct ANASTASIS_VersionCheck *vc); + + +/** * Defines a Decryption Policy with multiple escrow methods */ struct ANASTASIS_DecryptionPolicy @@ -510,9 +665,9 @@ struct ANASTASIS_Recovery; * * @param ctx context for making HTTP requests * @param id_data contains the users identity, (user account on providers) - * @param version defines the version which will be downloaded NULL for latest version - * @param anastasis_provider_url NULL terminated list of possible provider urls - * @param provider_salt the server salt + * @param version defines the version which will be downloaded, 0 for latest version + * @param anastasis_provider_url provider REST API endpoint url + * @param provider_salt the provider's salt * @param pc opens the policy call back which holds the downloaded version and the policies * @param pc_cls closure for callback * @param csc core secret callback is opened, with this the core secert is passed to the client after the authentication @@ -845,7 +1000,7 @@ struct ANASTASIS_ProviderSuccessStatus /** * When will the policy expire? */ - struct GNUNET_TIME_Absolute policy_expiration; + struct GNUNET_TIME_Timestamp policy_expiration; /** * Version number of the policy at the provider. @@ -916,7 +1071,6 @@ struct ANASTASIS_ShareResult */ enum ANASTASIS_UploadStatus ec; - } provider_failure; } details; diff --git a/src/include/anastasis_authorization_lib.h b/src/include/anastasis_authorization_lib.h index 975dd5f..bcbd2e6 100644 --- a/src/include/anastasis_authorization_lib.h +++ b/src/include/anastasis_authorization_lib.h @@ -3,7 +3,7 @@ Copyright (C) 2019, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY diff --git a/src/include/anastasis_authorization_plugin.h b/src/include/anastasis_authorization_plugin.h index 91a88f8..a9d993d 100644 --- a/src/include/anastasis_authorization_plugin.h +++ b/src/include/anastasis_authorization_plugin.h @@ -3,7 +3,7 @@ Copyright (C) 2019 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -33,21 +33,21 @@ struct ANASTASIS_AUTHORIZATION_State; /** * Enumeration values indicating the various possible - * outcomes of the plugin's `process` function. + * outcomes of the plugin's `challenge` function. */ -enum ANASTASIS_AUTHORIZATION_Result +enum ANASTASIS_AUTHORIZATION_ChallengeResult { /** * We successfully sent the authorization challenge * and queued a reply to MHD. */ - ANASTASIS_AUTHORIZATION_RES_SUCCESS = 0, + ANASTASIS_AUTHORIZATION_CRES_SUCCESS = 0, /** * We failed to transmit the authorization challenge, * but successfully queued a failure response to MHD. */ - ANASTASIS_AUTHORIZATION_RES_FAILED = 1, + ANASTASIS_AUTHORIZATION_CRES_FAILED = 1, /** * The plugin suspended the MHD connection as it needs some more @@ -55,7 +55,7 @@ enum ANASTASIS_AUTHORIZATION_Result * plugin will resume the MHD connection when its work is done, and * then the `process` function should be called again. */ - ANASTASIS_AUTHORIZATION_RES_SUSPENDED = 2, + ANASTASIS_AUTHORIZATION_CRES_SUSPENDED = 2, /** * The plugin tried to queue a reply on the MHD connection and @@ -65,7 +65,7 @@ enum ANASTASIS_AUTHORIZATION_Result * However, we were successful at transmitting the challenge, * so the challenge should be marked as sent. */ - ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED = 4, + ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED = 4, /** * The plugin tried to queue a reply on the MHD connection and @@ -74,14 +74,45 @@ enum ANASTASIS_AUTHORIZATION_Result * * Additionally, we failed to transmit the challenge. */ - ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED = 5, + ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED = 5 +}; + + +/** + * Enumeration values indicating the various possible + * outcomes of the plugin's `solve` function. + */ +enum ANASTASIS_AUTHORIZATION_SolveResult +{ + /** + * We failed to transmit the authorization challenge, + * but successfully queued a failure response to MHD. + */ + ANASTASIS_AUTHORIZATION_SRES_FAILED = 0, + + /** + * The plugin suspended the MHD connection as it needs some more + * time to do its (asynchronous) work before we can proceed. The + * plugin will resume the MHD connection when its work is done, and + * then the `process` function should be called again. + */ + ANASTASIS_AUTHORIZATION_SRES_SUSPENDED = 1, + + /** + * The plugin tried to queue a reply on the MHD connection and + * failed to do so. We should return #MHD_NO to MHD to cause the + * HTTP connection to be closed without any reply. + * + * Additionally, we failed to transmit the challenge. + */ + ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED = 2, /** * The authentication process completed successfully * and we should signal success to the client by * returning the truth. */ - ANASTASIS_AUTHORIZATION_RES_FINISHED = 6 + ANASTASIS_AUTHORIZATION_SRES_FINISHED = 3 }; @@ -127,6 +158,14 @@ struct ANASTASIS_AuthorizationPlugin bool payment_plugin_managed; /** + * The plugin expects the "code" in the "start" function to be + * provided by the user and not generated by the Anastasis + * backend. The plugin will then validate the code using its own + * means. Used by TOTP. + */ + bool user_provided_code; + + /** * How often are retries allowed for challenges created * by this plugin? */ @@ -202,18 +241,31 @@ struct ANASTASIS_AuthorizationPlugin /** * Continue issuing authentication challenge to user based on @a data. * I.e. check if the transmission of the challenge via SMS or e-mail - * has completed and/or manipulate @a connection to redirect the client - * to a video identification site. + * has completed and/or manipulate @a connection to direct the client towards solving the challenge. + * + * @param as authorization state + * @param connection HTTP client request (for queuing response, such as redirection to video portal) + * @return state of the request + */ + enum ANASTASIS_AUTHORIZATION_ChallengeResult + (*challenge)(struct ANASTASIS_AUTHORIZATION_State *as, + struct MHD_Connection *connection); + + + /** + * Check if the client has solved the challenge. * * @param as authorization state * @param timeout how long do we have to produce a reply + * @param challenge_response hash of the challenge response, or NULL * @param connection HTTP client request (for queuing response, such as redirection to video portal) * @return state of the request */ - enum ANASTASIS_AUTHORIZATION_Result - (*process)(struct ANASTASIS_AUTHORIZATION_State *as, - struct GNUNET_TIME_Absolute timeout, - struct MHD_Connection *connection); + enum ANASTASIS_AUTHORIZATION_SolveResult + (*solve)(struct ANASTASIS_AUTHORIZATION_State *as, + struct GNUNET_TIME_Absolute timeout, + const struct GNUNET_HashCode *challenge_response, + struct MHD_Connection *connection); /** diff --git a/src/include/anastasis_crypto_lib.h b/src/include/anastasis_crypto_lib.h index 6377baf..8cbc954 100644 --- a/src/include/anastasis_crypto_lib.h +++ b/src/include/anastasis_crypto_lib.h @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -21,7 +21,7 @@ * @author Dennis Neufeld */ #include <jansson.h> -#include <gnunet/gnunet_crypto_lib.h> +#include <gnunet/gnunet_util_lib.h> /** @@ -41,6 +41,12 @@ "Anastasis-Truth-Decryption-Key" /** + * Client to server: please store this meta data. + */ +#define ANASTASIS_HTTP_HEADER_POLICY_META_DATA "Anastasis-Policy-Meta-Data" + + +/** * Client to server: I paid using this payment secret. */ #define ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER "Anastasis-Payment-Identifier" @@ -133,34 +139,25 @@ struct ANASTASIS_CRYPTO_PolicyKeyP /** - * Specifies an encrypted master key, the key is used to encrypt the core secret from the user - */ -struct ANASTASIS_CRYPTO_EncryptedMasterKeyP -{ - struct GNUNET_HashCode key GNUNET_PACKED; -}; - - -/** - * Specifies a Nonce used for the AES encryption, here defined as 32Byte large. + * Nonce used for encryption, 24 bytes. */ struct ANASTASIS_CRYPTO_NonceP { - uint32_t nonce[8]; + uint8_t nonce[crypto_secretbox_NONCEBYTES]; }; /** - * Specifies an IV used for the AES encryption, here defined as 16Byte large. + * Header that is prepended to a ciphertext, consisting of nonce and MAC. */ -struct ANASTASIS_CRYPTO_IvP +struct ANASTASIS_CRYPTO_CiphertextHeaderP { - uint32_t iv[4]; + uint8_t header[crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES]; }; /** - * Specifies an symmetric key used for the AES encryption, here defined as 32Byte large. + * Specifies a key used for symmetric encryption, 32 bytes. */ struct ANASTASIS_CRYPTO_SymKeyP { @@ -169,15 +166,6 @@ struct ANASTASIS_CRYPTO_SymKeyP /** - * Specifies an AES Tag used for the AES authentication, here defined as 16 Byte large. - */ -struct ANASTASIS_CRYPTO_AesTagP -{ - uint32_t aes_tag[4]; -}; - - -/** * Specifies a Key Share from an escrow provider, the combined * keyshares generate the EscrowMasterKey which is used to decrypt the * Secret from the user. @@ -194,17 +182,12 @@ struct ANASTASIS_CRYPTO_KeyShareP struct ANASTASIS_CRYPTO_EncryptedKeyShareP { /** - * Nonce used for the symmetric encryption. - */ - struct ANASTASIS_CRYPTO_NonceP nonce; - - /** - * GCM tag to check authenticity. + * Ciphertext. */ - struct ANASTASIS_CRYPTO_AesTagP tag; + struct ANASTASIS_CRYPTO_CiphertextHeaderP header; /** - * The actual key share. + * The actual key share, encrypted. */ struct ANASTASIS_CRYPTO_KeyShareP keyshare; }; @@ -271,6 +254,33 @@ struct ANASTASIS_AccountSignatureP GNUNET_NETWORK_STRUCT_END +/** + * Result of encrypting the core secret. + */ +struct ANASTASIS_CoreSecretEncryptionResult +{ + /** + * Encrypted core secret. + */ + void *enc_core_secret; + + /** + * Size of the encrypted core secret. + */ + size_t enc_core_secret_size; + + /** + * Array of encrypted master keys. Each key is encrypted + * to a different policy key. + */ + void **enc_master_keys; + + /** + * Sizes of the encrypted master keys. + */ + size_t *enc_master_key_sizes; +}; + /** * Hash a numerical answer to compute the hash value to be submitted @@ -291,13 +301,13 @@ ANASTASIS_hash_answer (uint64_t code, * data. * * @param id_data JSON encoded data, which contains the raw user secret - * @param server_salt salt from the server (escrow provider) + * @param provider_salt salt from the server (escrow provider) * @param[out] id reference to the id which was created */ void ANASTASIS_CRYPTO_user_identifier_derive ( const json_t *id_data, - const struct ANASTASIS_CRYPTO_ProviderSaltP *server_salt, + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt, struct ANASTASIS_CRYPTO_UserIdentifierP *id); @@ -344,7 +354,7 @@ ANASTASIS_CRYPTO_secure_answer_hash ( /** - * Encrypt and signs the recovery document with AES256, the recovery + * Encrypt and signs the recovery document, the recovery * document is encrypted with a derivation from the user identifier * and the salt "erd". * @@ -365,7 +375,7 @@ ANASTASIS_CRYPTO_recovery_document_encrypt ( /** - * Decrypts the recovery document with AES256, the decryption key is generated with + * Decrypts the recovery document, the decryption key is generated with * the user identifier provided by the user and the salt "erd". The nonce and IV used for the encryption * are the first 48 bytes of the data. * @@ -386,6 +396,44 @@ ANASTASIS_CRYPTO_recovery_document_decrypt ( /** + * Encrypt recovery document meta data. + * + * @param id Hashed User input, used for the generation of the encryption key + * @param meta_data contains the recovery document meta data + * @param meta_data_size number of bytes in @a meta_data + * @param[out] enc_meta_data set to the encrypted meta data + * @param[out] enc_meta_data_size size of the result + */ +void +ANASTASIS_CRYPTO_recovery_metadata_encrypt ( + const struct ANASTASIS_CRYPTO_UserIdentifierP *id, + const void *meta_data, + size_t meta_data_size, + void **enc_meta_data, + size_t *enc_meta_data_size); + + +/** + * Decrypts the recovery meta data. + * + * @param id Hashed User input, used for the generation of the decryption key + * @param enc_meta_data encrypted meta data + * @param enc_meta_data_size number of bytes in @a enc_meta_data + * @param[out] meta_data decrypted meta data + * @param[out] meta_data_size size of the result in @a meta_data + * @return #GNUNET_OK on success, #GNUNET_NO if the authentication tag + * was wrong + */ +enum GNUNET_GenericReturnValue +ANASTASIS_CRYPTO_recovery_metadata_decrypt ( + const struct ANASTASIS_CRYPTO_UserIdentifierP *id, + const void *enc_meta_data, + size_t enc_meta_data_size, + void **meta_data, + size_t *meta_data_size); + + +/** * Encrypts a keyshare with a key generated with the user identification as entropy and the salt "eks". * * @param key_share the key share which is afterwards encrypted @@ -419,7 +467,7 @@ ANASTASIS_CRYPTO_keyshare_decrypt ( /** * Encrypts the truth data which contains the hashed answer or the - * phone number. It is encrypted with AES256, the key is generated + * phone number. It is encrypted with xsalsa20-poly1305, the key is generated * with the user identification as entropy source and the salt "ect". * * @param nonce value to use for the nonce @@ -442,7 +490,7 @@ ANASTASIS_CRYPTO_truth_encrypt ( /** * Decrypts the truth data which contains the hashed answer or the phone number.. - * It is decrypted with AES256, the key is generated with the user identification as + * It is decrypted with xsalsa20-poly1305, the key is generated with the user identification as * entropy source and the salt "ect". * * @param truth_enc_key master key used for encryption of the truth (see interface EscrowMethod) @@ -492,24 +540,30 @@ ANASTASIS_CRYPTO_policy_key_derive ( * The core secret is the user provided secret which will be saved with Anastasis. * The secret will be encrypted with the master key, the master key is a random key which will * be generated. The master key afterwards will be encrypted with the different policy keys. - * Encryption is performed with AES256 + * Encryption is performed with xsalsa20-poly1305. * * @param policy_keys an array of policy keys which are used to encrypt the master key * @param policy_keys_length defines the amount of policy keys and also the amount of encrypted master keys * @param core_secret the user provided core secret which is secured by anastasis * @param core_secret_size the size of the core secret - * @param[out] enc_core_secret the core secret is encrypted with the generated master key - * @param[out] encrypted_master_keys array of encrypted master keys which will be safed inside the policies one encrypted - * master key is created for each policy key + * @returns result of the encryption, must be freed with #ANASTASIS_CRYPTO_destroy_encrypted_core_secret */ -void +struct ANASTASIS_CoreSecretEncryptionResult * ANASTASIS_CRYPTO_core_secret_encrypt ( const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_keys, unsigned int policy_keys_length, const void *core_secret, - size_t core_secret_size, - void **enc_core_secret, - struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_keys); + size_t core_secret_size); + + +/** + * Destroy a core secret encryption result. + * + * @param cser the result to destroy + */ +void +ANASTASIS_CRYPTO_destroy_encrypted_core_secret ( + struct ANASTASIS_CoreSecretEncryptionResult *cser); /** @@ -517,6 +571,7 @@ ANASTASIS_CRYPTO_core_secret_encrypt ( * Afterwards the core secret is encrypted with the master key. The core secret is returned. * * @param encrypted_master_key master key for decrypting the core secret, is itself encrypted by the policy key + * @param encrypted_master_key_size size of the encrypted master key * @param policy_key built policy key which will decrypt the master key * @param encrypted_core_secret the encrypted core secret from the user, will be encrypted with the policy key * @param encrypted_core_secret_size size of the encrypted core secret @@ -525,9 +580,23 @@ ANASTASIS_CRYPTO_core_secret_encrypt ( */ void ANASTASIS_CRYPTO_core_secret_recover ( - const struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_key, + const void *encrypted_master_key, + size_t encrypted_master_key_size, const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key, const void *encrypted_core_secret, size_t encrypted_core_secret_size, void **core_secret, size_t *core_secret_size); + + +/** + * Convert a @a uuid to a shortened, human-readable string + * useful to show to users to identify the truth. + * Note that the return value is in a global variable and + * only valid until the next invocation of this function. + * + * @param uuid UUID to convert + * @return string representation + */ +const char * +ANASTASIS_CRYPTO_uuid2s (const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid); diff --git a/src/include/anastasis_database_lib.h b/src/include/anastasis_database_lib.h index 7de1612..896b039 100644 --- a/src/include/anastasis_database_lib.h +++ b/src/include/anastasis_database_lib.h @@ -3,7 +3,7 @@ Copyright (C) 2019 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h index 7bf91a2..2082bf9 100644 --- a/src/include/anastasis_database_plugin.h +++ b/src/include/anastasis_database_plugin.h @@ -1,9 +1,9 @@ /* This file is part of Anastasis - Copyright (C) 2019-2021 Anastasis SARL + Copyright (C) 2019-2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -136,7 +136,7 @@ enum ANASTASIS_DB_StoreStatus typedef void (*ANASTASIS_DB_PaymentPendingIterator)( void *cls, - struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Timestamp timestamp, const struct ANASTASIS_PaymentSecretP *payment_secret, const struct TALER_Amount *amount); @@ -159,6 +159,26 @@ typedef bool /** + * Function called on matching meta data. Note that if the client did + * not provide meta data for @a version, the function will be called + * with @a recovery_meta_data being NULL. + * + * @param cls closure + * @param version the version of the recovery document + * @param ts timestamp when the document was uploaded + * @param recovery_meta_data contains meta data about the encrypted recovery document + * @param recovery_meta_data_size size of @a recovery_meta_data blob + * @return #GNUNET_OK to continue to iterate, #GNUNET_NO to abort iteration + */ +typedef enum GNUNET_GenericReturnValue +(*ANASTASIS_DB_RecoveryMetaCallback)(void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp ts, + const void *recovery_meta_data, + size_t recovery_meta_data_size); + + +/** * Handle to interact with the database. * * Functions ending with "_TR" run their OWN transaction scope @@ -329,6 +349,8 @@ struct ANASTASIS_DatabasePlugin * @param recovery_data_hash hash of @a data * @param recovery_data contains encrypted recovery document * @param recovery_data_size size of @a recovery_data blob + * @param recovery_meta_data contains meta data about the encrypted recovery document + * @param recovery_meta_data_size size of @a recovery_meta_data blob * @param payment_secret identifier for the payment, used to later charge on uploads * @param[out] version set to the version assigned to the document by the database * @return transaction status, 0 if upload could not be finished because @a payment_secret @@ -342,11 +364,34 @@ struct ANASTASIS_DatabasePlugin const struct GNUNET_HashCode *recovery_data_hash, const void *recovery_data, size_t recovery_data_size, + const void *recovery_meta_data, + size_t recovery_meta_data_size, const struct ANASTASIS_PaymentSecretP *payment_secret, uint32_t *version); /** + * Fetch recovery document meta data for user. Returns + * meta data in descending order from @a max_version. + * The size of the result set may be limited. + * + * @param cls closure + * @param account_pub public key of the user's account + * @param max_version the maximum version number the user requests + * @param cb function to call on each result + * @param cb_cls closure for @a cb + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*get_recovery_meta_data)( + void *cls, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, + uint32_t max_version, + ANASTASIS_DB_RecoveryMetaCallback cb, + void *cb_cls); + + + /** * Fetch recovery document for user according given version. * * @param cls closure @@ -468,7 +513,7 @@ struct ANASTASIS_DatabasePlugin (*lookup_account)( void *cls, const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, - struct GNUNET_TIME_Absolute *paid_until, + struct GNUNET_TIME_Timestamp *paid_until, struct GNUNET_HashCode *recovery_data_hash, uint32_t *version); @@ -525,7 +570,7 @@ struct ANASTASIS_DatabasePlugin const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, const struct ANASTASIS_PaymentSecretP *payment_identifier, struct GNUNET_TIME_Relative lifetime, - struct GNUNET_TIME_Absolute *paid_until); + struct GNUNET_TIME_Timestamp *paid_until); /** @@ -543,7 +588,7 @@ struct ANASTASIS_DatabasePlugin void *cls, const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, const struct ANASTASIS_PaymentSecretP *payment_identifier, - struct GNUNET_TIME_Absolute eol); + struct GNUNET_TIME_Timestamp eol); /** @@ -596,7 +641,7 @@ struct ANASTASIS_DatabasePlugin (*check_truth_upload_paid)( void *cls, const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, - struct GNUNET_TIME_Absolute *paid_until); + struct GNUNET_TIME_Timestamp *paid_until); /** @@ -633,7 +678,7 @@ struct ANASTASIS_DatabasePlugin (*mark_challenge_code_satisfied)( void *cls, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - const uint64_t code); + uint64_t code); /** @@ -653,7 +698,7 @@ struct ANASTASIS_DatabasePlugin void *cls, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, const uint64_t code, - struct GNUNET_TIME_Absolute after); + struct GNUNET_TIME_Timestamp after); /** @@ -679,7 +724,7 @@ struct ANASTASIS_DatabasePlugin struct GNUNET_TIME_Relative rotation_period, struct GNUNET_TIME_Relative validity_period, uint32_t retry_counter, - struct GNUNET_TIME_Absolute *retransmission_date, + struct GNUNET_TIME_Timestamp *retransmission_date, uint64_t *code); @@ -780,7 +825,7 @@ struct ANASTASIS_DatabasePlugin const struct TALER_Amount *amount, const char *debit_account, const char *credit_account, - struct GNUNET_TIME_Absolute execution_date); + struct GNUNET_TIME_Timestamp execution_date); /** @@ -802,7 +847,7 @@ struct ANASTASIS_DatabasePlugin (*test_auth_iban_payment)( void *cls, const char *debit_account, - struct GNUNET_TIME_Absolute earliest_date, + struct GNUNET_TIME_Timestamp earliest_date, ANASTASIS_DB_AuthIbanTransfercheck cb, void *cb_cls); diff --git a/src/include/anastasis_eufin_lib.h b/src/include/anastasis_eufin_lib.h index daff98a..91b9fe3 100644 --- a/src/include/anastasis_eufin_lib.h +++ b/src/include/anastasis_eufin_lib.h @@ -112,7 +112,7 @@ struct ANASTASIS_EUFIN_CreditDetails /** * Time of the the transfer */ - struct GNUNET_TIME_Absolute execution_date; + struct GNUNET_TIME_Timestamp execution_date; /** * The wire transfer subject. diff --git a/src/include/anastasis_redux.h b/src/include/anastasis_redux.h index dd28174..2adb74b 100644 --- a/src/include/anastasis_redux.h +++ b/src/include/anastasis_redux.h @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -111,4 +111,111 @@ void ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra); +/** + * Handle for a policy discovery operation. + */ +struct ANASTASIS_PolicyDiscovery; + + +/** + * Function called on each discovered recovery policy. + * + * The client can then start a new policy discovery process, using the + * smallest (also most recent) @a version received per @a provider_url + * in the cursor to resume. Note that in this case, the application + * logic is responsible for de-duplication using @a hcpd, or it may show + * policies again if they are at different providers under versions not + * queried up to the cursor. + * + * @param cls closure + * @param hcpd hash of the compressed policy document (unique per policy) + * @param provider_url which provider claims to have this policy + * @param version version of the policy at this provider + * @param attribute_mask combination of optional identity attributes + * present in the state that was used to locate this version + * @param server_time when did the provider receive the upload + * @param secret_name name the user assigned to the backup + * @param providers json array of providers with this policy + */ +typedef void +(*ANASTASIS_PolicyDiscoveryCallback)(void *cls, + const struct GNUNET_HashCode *hcpd, + const char *provider_url, + uint32_t version, + json_int_t attribute_mask, + struct GNUNET_TIME_Timestamp server_time, + const char *secret_name, + const json_t *providers); + + +/** + * Start requesting providers for available policies for the + * recovery specified in @a state. + * + * @param state state to discover polices in + * @param cursor array containing "provider_url", attribute "mask", + * and "max_version" values (max_version is exclusive). + * Used for incremental discovery, NULL is allowed + * to begin from the latest version(s). + * @param cb function to call with results + * @param cb_cls closure for @a cb + * @return NULL on failure + */ +struct ANASTASIS_PolicyDiscovery * +ANASTASIS_policy_discovery_start (const json_t *state, + const json_t *cursor, + ANASTASIS_PolicyDiscoveryCallback cb, + void *cb_cls); + + +/** + * Add another provider to the list of providers to do discovery + * on. + * + * @param[in,out] pd policy discovery to expand + * @param provider_url the provider to add to the set of providers + * @param provider_state configuration state for that provider + */ +void +ANASTASIS_policy_discovery_more (struct ANASTASIS_PolicyDiscovery *pd, + const char *provider_url, + json_t *provider_state); + +/** + * Stop policy discovery. + * + * @param[in] pd operation to stop + */ +void +ANASTASIS_policy_discovery_stop (struct ANASTASIS_PolicyDiscovery *pd); + + +/** + * Compute a subset of @a master_id removing optional attributes + * based on the bits set in @a mask. + * + * @param state reducer state (tells us which attributes are optional) + * @param master_id set of identity attributes to mask + * @param mask bitmask to apply + * @return masked copy of the @a master_id + */ +json_t * +ANASTASIS_mask_id_data (const json_t *state, + const json_t *master_id, + json_int_t mask); + +/** + * Lookup @a salt of @a provider_url in @a state. + * + * @param state the state to inspect + * @param provider_url provider to look into + * @param[out] salt value to extract + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +ANASTASIS_reducer_lookup_salt (const json_t *state, + const char *provider_url, + struct ANASTASIS_CRYPTO_ProviderSaltP *salt); + + #endif /* _ANASTASIS_REDUX_H */ diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h index bec89d1..2f30a8b 100644 --- a/src/include/anastasis_service.h +++ b/src/include/anastasis_service.h @@ -1,16 +1,16 @@ /* This file is part of Anastasis - Copyright (C) 2019-2021 Anastasis SARL + Copyright (C) 2019-2022 Anastasis SARL Anastasis 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 + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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 Lesser General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Lesser General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/> */ /** @@ -51,56 +51,82 @@ struct ANASTASIS_AuthorizationMethodConfig */ struct ANASTASIS_Config { - /** - * Protocol version supported by the server. - */ - const char *version; /** - * Business name of the anastasis provider. + * HTTP status returned. */ - const char *business_name; + unsigned int http_status; /** - * Currency used for payments by the server. + * Taler-specific error code, #TALER_EC_NONE on success. */ - const char *currency; + enum TALER_ErrorCode ec; /** - * Array of authorization methods supported by the server. + * Full response in JSON, if provided. */ - const struct ANASTASIS_AuthorizationMethodConfig *methods; + const json_t *response; /** - * Length of the @e methods array. + * Details depending on @e http_status. */ - unsigned int methods_length; + union + { - /** - * Maximum size of an upload in megabytes. - */ - uint32_t storage_limit_in_megabytes; + /** + * Details on #MHD_HTTP_OK. + */ + struct + { - /** - * Annual fee for an account / policy upload. - */ - struct TALER_Amount annual_fee; + /** + * Protocol version supported by the server. + */ + const char *version; - /** - * Fee for a truth upload. - */ - struct TALER_Amount truth_upload_fee; + /** + * Business name of the anastasis provider. + */ + const char *business_name; - /** - * Maximum legal liability for data loss covered by the - * provider. - */ - struct TALER_Amount liability_limit; + /** + * Array of authorization methods supported by the server. + */ + const struct ANASTASIS_AuthorizationMethodConfig *methods; - /** - * Server salt. - */ - struct ANASTASIS_CRYPTO_ProviderSaltP salt; + /** + * Length of the @e methods array. + */ + unsigned int methods_length; + + /** + * Maximum size of an upload in megabytes. + */ + uint32_t storage_limit_in_megabytes; + + /** + * Annual fee for an account / policy upload. + */ + struct TALER_Amount annual_fee; + + /** + * Fee for a truth upload. + */ + struct TALER_Amount truth_upload_fee; + + /** + * Maximum legal liability for data loss covered by the + * provider. + */ + struct TALER_Amount liability_limit; + + /** + * Provider salt. + */ + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + } ok; + + } details; }; @@ -112,12 +138,10 @@ struct ANASTASIS_Config * the server provided an acceptable response. * * @param cls closure - * @param http_status the HTTP status * @param acfg configuration obtained, NULL if we could not parse it */ typedef void (*ANASTASIS_ConfigCallback)(void *cls, - unsigned int http_status, const struct ANASTASIS_Config *acfg); @@ -156,34 +180,181 @@ ANASTASIS_config_cancel (struct ANASTASIS_ConfigOperation *co); /** - * Detailed results from the successful download. + * Detailed meta data result. */ -struct ANASTASIS_DownloadDetails +struct ANASTASIS_MetaDataEntry { + + /** + * Timestamp of the backup at the server. + */ + struct GNUNET_TIME_Timestamp server_time; + /** - * Signature (already verified). + * The encrypted meta data we downloaded. */ - struct ANASTASIS_AccountSignatureP sig; + const void *meta_data; /** - * Hash over @e policy and @e policy_size. + * Number of bytes in @e meta_data. */ - struct GNUNET_HashCode curr_policy_hash; + size_t meta_data_size; /** - * The backup we downloaded. + * Policy version this @e meta_data is for. */ - const void *policy; + uint32_t version; +}; + + +/** + * Detailed results for meta data download. + */ +struct ANASTASIS_MetaDownloadDetails +{ /** - * Number of bytes in @e backup. + * HTTP status returned. */ - size_t policy_size; + unsigned int http_status; /** - * Policy version returned by the service. + * Taler-specific error code, #TALER_EC_NONE on success. */ - uint32_t version; + enum TALER_ErrorCode ec; + + /** + * Full response in JSON, if provided. + */ + const json_t *response; + + /** + * Details depending on @e http_status. + */ + union + { + + /** + * Details on #MHD_HTTP_OK. + */ + struct + { + + /** + * Version-sorted array of meta data we downloaded. + */ + const struct ANASTASIS_MetaDataEntry *metas; + + /** + * Number of entries in @e metas. + */ + size_t metas_length; + + } ok; + + } details; +}; + + +/** + * Callback to process a GET /policy/$POL/meta request + * + * @param cls closure + * @param dd the response details + */ +typedef void +(*ANASTASIS_PolicyMetaLookupCallback) ( + void *cls, + const struct ANASTASIS_MetaDownloadDetails *dd); + + +/** + * Does a GET /policy/$POL/meta. + * + * @param ctx execution context + * @param backend_url base URL of the merchant backend + * @param anastasis_pub public key of the user's account + * @param max_version maximum version number to fetch + * @param cb callback which will work the response gotten from the backend + * @param cb_cls closure to pass to the callback + * @return handle for this operation, NULL upon errors + */ +struct ANASTASIS_PolicyMetaLookupOperation * +ANASTASIS_policy_meta_lookup ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub, + uint32_t max_version, + ANASTASIS_PolicyMetaLookupCallback cb, + void *cb_cls); + + +/** + * Cancel a GET /policy/$POL/meta request. + * + * @param plo cancel the policy lookup operation + */ +void +ANASTASIS_policy_meta_lookup_cancel ( + struct ANASTASIS_PolicyMetaLookupOperation *plo); + + +/** + * Detailed results from the successful download. + */ +struct ANASTASIS_DownloadDetails +{ + + /** + * HTTP status returned. + */ + unsigned int http_status; + + /** + * Taler-specific error code, #TALER_EC_NONE on success. + */ + enum TALER_ErrorCode ec; + + /** + * Details depending on @e http_status. + */ + union + { + + /** + * Details on #MHD_HTTP_OK. + */ + struct + { + + /** + * Signature (already verified). + */ + struct ANASTASIS_AccountSignatureP sig; + + /** + * Hash over @e policy and @e policy_size. + */ + struct GNUNET_HashCode curr_policy_hash; + + /** + * The backup we downloaded. + */ + const void *policy; + + /** + * Number of bytes in @e backup. + */ + size_t policy_size; + + /** + * Policy version returned by the service. + */ + uint32_t version; + } ok; + + } details; + }; @@ -197,13 +368,10 @@ struct ANASTASIS_PolicyLookupOperation; * Callback to process a GET /policy request * * @param cls closure - * @param http_status HTTP status code for this request - * @param ec anastasis-specific error code - * @param obj the response body + * @param dd the response details */ typedef void (*ANASTASIS_PolicyLookupCallback) (void *cls, - unsigned int http_status, const struct ANASTASIS_DownloadDetails *dd); @@ -337,7 +505,7 @@ struct ANASTASIS_UploadDetails * At what time is the provider set to forget this * policy (because the account expires)? */ - struct GNUNET_TIME_Absolute policy_expiration; + struct GNUNET_TIME_Timestamp policy_expiration; /** * Version number of the resulting policy. @@ -371,8 +539,7 @@ struct ANASTASIS_UploadDetails * Callback to process a POST /policy request * * @param cls closure - * @param http_status HTTP status code for this request - * @param obj the decoded response body + * @param up the decoded response body */ typedef void (*ANASTASIS_PolicyStoreCallback) (void *cls, @@ -387,6 +554,8 @@ typedef void * @param anastasis_priv private key of the user's account * @param recovery_data policy data to be stored * @param recovery_data_size number of bytes in @a recovery_data + * @param recovery_meta_data policy meta data to be stored + * @param recovery_meta_data_size number of bytes in @a recovery_meta_data * @param payment_years_requested for how many years would the client like the service to store the truth? * @param payment_secret payment identifier of last payment * @param payment_timeout how long to wait for the payment, use @@ -402,6 +571,8 @@ ANASTASIS_policy_store ( const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv, const void *recovery_data, size_t recovery_data_size, + const void *recovery_meta_data, + size_t recovery_meta_data_size, uint32_t payment_years_requested, const struct ANASTASIS_PaymentSecretP *payment_secret, struct GNUNET_TIME_Relative payment_timeout, @@ -423,299 +594,418 @@ ANASTASIS_policy_store_cancel ( /** - * Operational status. + * Handle for a POST /truth operation. + */ +struct ANASTASIS_TruthStoreOperation; + + +/** + * Callback to process a POST /truth request + * + * @param cls closure + * @param obj the response body + */ +typedef void +(*ANASTASIS_TruthStoreCallback) (void *cls, + const struct ANASTASIS_UploadDetails *up); + + +/** + * Store Truth, does a POST /truth/$UUID + * + * @param ctx the CURL context used to connect to the backend + * @param backend_url backend's base URL, including final "/" + * @param uuid unique identfication of the Truth Upload + * @param type type of the authorization method + * @param encrypted_keyshare key material to return to the client upon authorization + * @param truth_mime mime type of @e encrypted_truth (after decryption) + * @param encrypted_truth_size number of bytes in @e encrypted_truth + * @param encrypted_truth contains the @a type-specific authorization data + * @param payment_years_requested for how many years would the client like the service to store the truth? + * @param payment_timeout how long to wait for the payment, use + * #GNUNET_TIME_UNIT_ZERO to let the server pick + * @param cb callback processing the response from /truth + * @param cb_cls closure for cb + * @return handle for the operation + */ +struct ANASTASIS_TruthStoreOperation * +ANASTASIS_truth_store ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, + const char *type, + const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *encrypted_keyshare, + const char *truth_mime, + size_t encrypted_truth_size, + const void *encrypted_truth, + uint32_t payment_years_requested, + struct GNUNET_TIME_Relative payment_timeout, + ANASTASIS_TruthStoreCallback cb, + void *cb_cls); + + +/** + * Cancel a POST /truth request. + * + * @param tso the truth store operation + */ +void +ANASTASIS_truth_store_cancel ( + struct ANASTASIS_TruthStoreOperation *tso); + + +/** + * Possible ways how to proceed with a challenge. */ -enum ANASTASIS_KeyShareDownloadStatus +enum ANASTASIS_ChallengeDetailType { - /** - * We got the encrypted key share. - */ - ANASTASIS_KSD_SUCCESS = 0, /** - * Payment is needed to proceed with the recovery. + * A challenge TAN was written to a file. + * The name of the file is provided. */ - ANASTASIS_KSD_PAYMENT_REQUIRED, + ANASTASIS_CS_FILE_WRITTEN, /** - * The provided answer was wrong or missing. Instructions for - * getting a good answer may be provided. + * A challenge TAN was sent to the customer. + * A hint may be provided as to the address used. */ - ANASTASIS_KSD_INVALID_ANSWER, + ANASTASIS_CS_TAN_SENT, /** - * To answer the challenge, the client should be redirected to - * the given URL. + * A challenge TAN was already recently sent to the customer. + * A hint may be provided as to the address used. */ - ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION, + ANASTASIS_CS_TAN_ALREADY_SENT, /** - * The provider had an error. + * The customer should wire funds to the bank + * account address provided. */ - ANASTASIS_KSD_SERVER_ERROR, + ANASTASIS_CS_WIRE_FUNDS + +}; + + +/** + * This structure contains information about where to wire the funds + * to authenticate as well as a hint as to which bank account to send + * the funds from. + */ +struct ANASTASIS_WireFundsDetails +{ /** - * The provider claims we made an error. + * Answer code expected. */ - ANASTASIS_KSD_CLIENT_FAILURE, + uint64_t answer_code; /** - * The provider does not know this truth. + * How much should be sent. */ - ANASTASIS_KSD_TRUTH_UNKNOWN, + struct TALER_Amount amount; /** - * Too many attempts to solve the challenge were made in a short - * time. Try again later. + * IBAN where to send the funds. */ - ANASTASIS_KSD_RATE_LIMIT_EXCEEDED, + const char *target_iban; /** - * The user did not satisfy the (external) - * authentication check until the request timeout - * was reached. The client should try again later. + * Name of the business receiving the funds. */ - ANASTASIS_KSD_AUTHENTICATION_TIMEOUT, + const char *target_business_name; /** - * The plugin provided external challenge instructions - * that should be followed. They are method-specific. + * Wire transfer subject to use. */ - ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS + const char *wire_transfer_subject; }; /** - * Detailed results from the successful download. + * Information returned for a POST /truth/$TID/challenge request. */ -struct ANASTASIS_KeyShareDownloadDetails +struct ANASTASIS_TruthChallengeDetails { + /** + * HTTP status returned by the server. + */ + unsigned int http_status; /** - * Operational status. + * Taler-specific error code, #TALER_EC_NONE on success. */ - enum ANASTASIS_KeyShareDownloadStatus status; + enum TALER_ErrorCode ec; /** - * Anastasis URL that returned the @e status. + * Full response in JSON, if provided. */ - const char *server_url; + const json_t *response; /** - * Details depending on @e status. + * Details depending on @e http_status. */ union { /** - * The encrypted key share (if @e status is #ANASTASIS_KSD_SUCCESS). - */ - struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks; - - /** - * Response if the challenge still needs to be answered, and the - * instructions are provided inline (no redirection). + * Information for @e http_status of #MHD_HTTP_OK. */ struct { - - /** - * HTTP status returned by the server. #MHD_HTTP_ALREADY_REPORTED - * if the server did already send the challenge to the user, - * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing). - */ - unsigned int http_status; - - /** - * Response with server-side reply containing instructions for the user - */ - const char *body; - /** - * Content-type: mime type of @e body, NULL if server did not provide any. + * Meta-state about how the challenge was + * initiated and what is to be done next. */ - const char *content_type; + enum ANASTASIS_ChallengeDetailType cs; /** - * Number of bytes in @e body. + * Details depending on @e cs. */ - size_t body_size; - - } open_challenge; + union + { + + /** + * If @e cs is #ANASTASIS_CS_FILE_WRITTEN, this + * is the filename with the challenge code. + */ + const char *challenge_filename; + + /** + * If @e cs is #ANASTASIS_CS_TAN_SENT, this + * is human-readable information as to where + * the TAN was sent. + */ + const char *tan_address_hint; + + /** + * If @e cs is #ANASTASIS_CS_WIRE_FUNDS, this + * structure contains information about where + * to wire the funds to authenticate as well + * as a hint as to which bank account to send + * the funds from. + */ + struct ANASTASIS_WireFundsDetails wire_funds; + + } details; - /** - * URL with instructions for the user to satisfy the challenge, if - * @e status is #ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION. - */ - const char *redirect_url; + } success; /** - * Response with instructions for how to pay, if - * @e status is #ANASTASIS_KSD_PAYMENT_REQUIRED. + * Information returne if @e http_status is #MHD_HTTP_PAYMENT_REQUIRED */ struct { - - /** - * "taler://pay" URL with details how to pay for the challenge. - */ - const char *taler_pay_uri; - /** - * The order ID from @e taler_pay_uri. + * A taler://pay/-URI with a request to pay the annual fee for + * the service. Returned if @e us is #ANASTASIS_US_PAYMENT_REQUIRED. */ - struct ANASTASIS_PaymentSecretP payment_secret; - - } payment_required; - - - /** - * Response with details about a server-side failure, if - * @e status is #ANASTASIS_KSD_SERVER_ERROR, - * #ANASTASIS_KSD_CLIENT_FAILURE or #ANASTASIS_KSD_TRUTH_UNKNOWN. - */ - struct - { + const char *payment_request; /** - * HTTP status returned by the server. + * The payment secret (aka order ID) extracted from the @e payment_request. */ - unsigned int http_status; + struct ANASTASIS_PaymentSecretP ps; /** - * Taler-specific error code. + * Data extracted from the payto:// URI. */ - enum TALER_ErrorCode ec; - - } server_failure; + const struct TALER_MERCHANT_PayUriData *pd; - /** - * External challenge instructions, if @e status is - * #ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS. - */ - const json_t *external_challenge; + } payment_required; } details; + }; /** - * Handle for a GET /truth operation. + * Handle for a POST /truth/$TID/challenge operation. */ -struct ANASTASIS_KeyShareLookupOperation; +struct ANASTASIS_TruthChallengeOperation; /** - * Callback to process a GET /truth request + * Callback to process a POST /truth/$TID/challenge response. * * @param cls closure - * @param http_status HTTP status code for this request - * @param kdd details about the key share + * @param tcd details about the key share */ typedef void -(*ANASTASIS_KeyShareLookupCallback) ( +(*ANASTASIS_TruthChallengeCallback) ( void *cls, - const struct ANASTASIS_KeyShareDownloadDetails *kdd); + const struct ANASTASIS_TruthChallengeDetails *tcd); /** - * Does a GET /truth. + * Makes a POST /truth/$TID/challenge request. * * @param ctx execution context * @param backend_url base URL of the merchant backend * @param truth_uuid identification of the Truth * @param truth_key Key used to Decrypt the Truth on the Server * @param payment_secret secret from the previously done payment NULL to trigger payment - * @param timeout how long to wait for the payment, use - * #GNUNET_TIME_UNIT_ZERO to let the server pick - * @param hashed_answer hashed answer to the challenge * @param cb callback which will work the response gotten from the backend * @param cb_cls closure to pass to the callback * @return handle for this operation, NULL upon errors */ -struct ANASTASIS_KeyShareLookupOperation * -ANASTASIS_keyshare_lookup ( +struct ANASTASIS_TruthChallengeOperation * +ANASTASIS_truth_challenge ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, const struct ANASTASIS_PaymentSecretP *payment_secret, - struct GNUNET_TIME_Relative timeout, - const struct GNUNET_HashCode *hashed_answer, - ANASTASIS_KeyShareLookupCallback cb, + ANASTASIS_TruthChallengeCallback cb, void *cb_cls); /** - * Cancel a GET /truth request. + * Cancel a POST /truth/$TID/challenge request. * - * @param kslo cancel the key share lookup operation + * @param[in] tco operation to cancel */ void -ANASTASIS_keyshare_lookup_cancel ( - struct ANASTASIS_KeyShareLookupOperation *kslo); +ANASTASIS_truth_challenge_cancel ( + struct ANASTASIS_TruthChallengeOperation *tco); /** - * Handle for a POST /truth operation. + * Information returned for a POST /truth/$TID/solve request. */ -struct ANASTASIS_TruthStoreOperation; +struct ANASTASIS_TruthSolveReply +{ + + /** + * HTTP status returned by the server. + */ + unsigned int http_status; + + /** + * Taler-specific error code, #TALER_EC_NONE on success. + */ + enum TALER_ErrorCode ec; + + /** + * Details depending on @e http_status. + */ + union + { + + /** + * Information returned if @e http_status is #MHD_HTTP_OK. + */ + struct + { + + /** + * The encrypted key share. + */ + struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks; + + } success; + + /** + * Information returne if @e http_status is #MHD_HTTP_PAYMENT_REQUIRED + */ + struct + { + /** + * A taler://pay/-URI with a request to pay the annual fee for + * the service. Returned if @e us is #ANASTASIS_US_PAYMENT_REQUIRED. + */ + const char *payment_request; + + /** + * The payment secret (aka order ID) extracted from the @e payment_request. + */ + struct ANASTASIS_PaymentSecretP ps; + + /** + * Data extracted from the payto:// URI. + */ + const struct TALER_MERCHANT_PayUriData *pd; + + } payment_required; + + /** + * Information returne if @e http_status is #MHD_HTTP_TOO_MANY_REQUESTS. + */ + struct + { + + /** + * How many requests are allowed at most per @e request_frequency? + */ + uint32_t request_limit; + + /** + * Frequency at which requests are allowed / new challenges are + * created. + */ + struct GNUNET_TIME_Relative request_frequency; + } too_many_requests; + + } details; + +}; /** - * Callback to process a POST /truth request + * Handle for a POST /truth/$TID/solve operation. + */ +struct ANASTASIS_TruthSolveOperation; + + +/** + * Callback to process a POST /truth/$TID/solve response. * * @param cls closure - * @param obj the response body + * @param kdd details about the key share */ typedef void -(*ANASTASIS_TruthStoreCallback) (void *cls, - const struct ANASTASIS_UploadDetails *up); +(*ANASTASIS_TruthSolveCallback) ( + void *cls, + const struct ANASTASIS_TruthSolveReply *trs); /** - * Store Truth, does a POST /truth/$UUID + * Makes a POST /truth/$TID/solve request. * - * @param ctx the CURL context used to connect to the backend - * @param backend_url backend's base URL, including final "/" - * @param uuid unique identfication of the Truth Upload - * @param type type of the authorization method - * @param encrypted_keyshare key material to return to the client upon authorization - * @param truth_mime mime type of @e encrypted_truth (after decryption) - * @param encrypted_truth_size number of bytes in @e encrypted_truth - * @param encrypted_truth contains the @a type-specific authorization data - * @param payment_years_requested for how many years would the client like the service to store the truth? - * @param payment_timeout how long to wait for the payment, use + * @param ctx execution context + * @param backend_url base URL of the merchant backend + * @param truth_uuid identification of the Truth + * @param truth_key Key used to Decrypt the Truth on the Server + * @param payment_secret secret from the previously done payment NULL to trigger payment + * @param timeout how long to wait for the payment, use * #GNUNET_TIME_UNIT_ZERO to let the server pick - * @param cb callback processing the response from /truth - * @param cb_cls closure for cb - * @return handle for the operation + * @param hashed_answer hashed answer to the challenge + * @param cb callback which will work the response gotten from the backend + * @param cb_cls closure to pass to the callback + * @return handle for this operation, NULL upon errors */ -struct ANASTASIS_TruthStoreOperation * -ANASTASIS_truth_store ( +struct ANASTASIS_TruthSolveOperation * +ANASTASIS_truth_solve ( struct GNUNET_CURL_Context *ctx, const char *backend_url, - const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, - const char *type, - const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *encrypted_keyshare, - const char *truth_mime, - size_t encrypted_truth_size, - const void *encrypted_truth, - uint32_t payment_years_requested, - struct GNUNET_TIME_Relative payment_timeout, - ANASTASIS_TruthStoreCallback cb, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, + const struct ANASTASIS_PaymentSecretP *payment_secret, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_HashCode *hashed_answer, + ANASTASIS_TruthSolveCallback cb, void *cb_cls); /** - * Cancel a POST /truth request. + * Cancel a POST /truth/$TID/solve request. * - * @param tso the truth store operation + * @param[in] tso handle of the operation to cancel */ void -ANASTASIS_truth_store_cancel ( - struct ANASTASIS_TruthStoreOperation *tso); +ANASTASIS_truth_solve_cancel ( + struct ANASTASIS_TruthSolveOperation *tso); #endif /* _ANASTASIS_SERVICE_H */ diff --git a/src/include/anastasis_testing_lib.h b/src/include/anastasis_testing_lib.h index a6c1fba..62cde06 100644 --- a/src/include/anastasis_testing_lib.h +++ b/src/include/anastasis_testing_lib.h @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -32,231 +32,125 @@ #define ANASTASIS_FAIL() \ do {GNUNET_break (0); return NULL; } while (0) -/** - * Index used in #ANASTASIS_TESTING_get_trait_hash() for the current hash. - */ -#define ANASTASIS_TESTING_TRAIT_HASH_CURRENT 0 - -/** - * Obtain a hash from @a cmd. - * - * @param cmd command to extract the number from. - * @param index the number's index number, use #ANASTASIS_TESTING_TRAIT_HASH_CURRENT - * @param[out] h set to the hash coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_hash (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct GNUNET_HashCode **h); - - -/** - * Offer a hash. - * - * @param index the number's index number. - * @param h the hash to offer. - * @return trait on success. - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_hash (unsigned int index, - const struct GNUNET_HashCode *h); - - -/** - * Obtain a truth decryption key from @a cmd. - * - * @param cmd command to extract the public key from. - * @param index usually 0 - * @param[out] key set to the account public key used in @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_truth_key ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthKeyP **key); - - -/** - * Offer an truth decryption key. - * - * @param index usually zero - * @param h the account_pub to offer. - * @return trait on success. - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_truth_key ( - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthKeyP *h); - - -/** - * Obtain an account public key from @a cmd. - * - * @param cmd command to extract the public key from. - * @param index usually 0 - * @param[out] pub set to the account public key used in @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_account_pub ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPublicKeyP **pub); - - -/** - * Offer an account public key. - * - * @param index usually zero - * @param h the account_pub to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_account_pub ( - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPublicKeyP *h); - - -/** - * Obtain an account private key from @a cmd. - * - * @param cmd command to extract the number from. - * @param index must be 0 - * @param[out] priv set to the account private key used in @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_account_priv ( - const struct - TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPrivateKeyP **priv); - /** - * Offer an account private key. - * - * @param index usually zero - * @param priv the account_priv to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_account_priv ( - unsigned int index, - const struct - ANASTASIS_CRYPTO_AccountPrivateKeyP *priv); - -/** - * Obtain an account public key from @a cmd. - * - * @param cmd command to extract the payment identifier from. - * @param index the payment identifier's index number. - * @param[out] payment_secret set to the payment secret coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_payment_secret ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_PaymentSecretP **payment_secret); - - -/** - * Offer a payment secret. - * - * @param index usually zero - * @param h the payment secret to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_payment_secret ( - unsigned int index, - const struct ANASTASIS_PaymentSecretP *h); + * Create headers for a trait with name @a name for + * statically allocated data of type @a type. + */ +#define ANASTASIS_TESTING_MAKE_DECL_SIMPLE_TRAIT(name,type) \ + enum GNUNET_GenericReturnValue \ + ANASTASIS_TESTING_get_trait_ ## name ( \ + const struct TALER_TESTING_Command *cmd, \ + type **ret); \ + struct TALER_TESTING_Trait \ + ANASTASIS_TESTING_make_trait_ ## name ( \ + type * value); + + +/** + * Create C implementation for a trait with name @a name for statically + * allocated data of type @a type. + */ +#define ANASTASIS_TESTING_MAKE_IMPL_SIMPLE_TRAIT(name,type) \ + enum GNUNET_GenericReturnValue \ + ANASTASIS_TESTING_get_trait_ ## name ( \ + const struct TALER_TESTING_Command *cmd, \ + type **ret) \ + { \ + if (NULL == cmd->traits) return GNUNET_SYSERR; \ + return cmd->traits (cmd->cls, \ + (const void **) ret, \ + TALER_S (name), \ + 0); \ + } \ + struct TALER_TESTING_Trait \ + ANASTASIS_TESTING_make_trait_ ## name ( \ + type * value) \ + { \ + struct TALER_TESTING_Trait ret = { \ + .trait_name = TALER_S (name), \ + .ptr = (const void *) value \ + }; \ + return ret; \ + } /** - * Obtain an truth UUID from @a cmd. - * - * @param cmd command to extract the number from. - * @param index the number's index number. - * @param[out] tpk set to the number coming from @a cmd. - * @return #GNUNET_OK on success. + * Create headers for a trait with name @a name for + * statically allocated data of type @a type. */ -int -ANASTASIS_TESTING_get_trait_truth_uuid ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthUUIDP **tpk); +#define ANASTASIS_TESTING_MAKE_DECL_INDEXED_TRAIT(name,type) \ + enum GNUNET_GenericReturnValue \ + ANASTASIS_TESTING_get_trait_ ## name ( \ + const struct TALER_TESTING_Command *cmd, \ + unsigned int index, \ + type **ret); \ + struct TALER_TESTING_Trait \ + ANASTASIS_TESTING_make_trait_ ## name ( \ + unsigned int index, \ + type * value); /** - * Offer a truth UUID. - * - * @param index the number's index number. - * @param tpk the UUID to offer. - * @return trait on success + * Create C implementation for a trait with name @a name for statically + * allocated data of type @a type. */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_truth_uuid ( - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthUUIDP *tpk); +#define ANASTASIS_TESTING_MAKE_IMPL_INDEXED_TRAIT(name,type) \ + enum GNUNET_GenericReturnValue \ + ANASTASIS_TESTING_get_trait_ ## name ( \ + const struct TALER_TESTING_Command *cmd, \ + unsigned int index, \ + type **ret) \ + { \ + if (NULL == cmd->traits) return GNUNET_SYSERR; \ + return cmd->traits (cmd->cls, \ + (const void **) ret, \ + TALER_S (name), \ + index); \ + } \ + struct TALER_TESTING_Trait \ + ANASTASIS_TESTING_make_trait_ ## name ( \ + unsigned int index, \ + type * value) \ + { \ + struct TALER_TESTING_Trait ret = { \ + .index = index, \ + .trait_name = TALER_S (name), \ + .ptr = (const void *) value \ + }; \ + return ret; \ + } /** - * Obtain an encrypted key share from @a cmd. - * - * @param cmd command to extract the number from. - * @param index the number's index number. - * @param[out] eks set to the key share coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_eks ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_EncryptedKeyShareP **eks); - - -/** - * Offer an encrypted key share. - * - * @param index the number's index number. - * @param eks the encrypted key share to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_eks ( - unsigned int index, - const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *eks); - - -/** - * Obtain a code from @a cmd. - * - * @param cmd command to extract the number from. - * @param index the number's index number. - * @param[out] code set to the number coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_code ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const char **code); - - -/** - * Offer an authentication code. - * - * @param index the number's index number. - * @param code the code to offer. - * @return trait on success + * Call #op on all simple traits. */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_code (unsigned int index, - const char *code); +#define ANASTASIS_TESTING_SIMPLE_TRAITS(op) \ + op (hash, const struct GNUNET_HashCode) \ + op (truth, const struct ANASTASIS_Truth *) \ + op (policy, const struct ANASTASIS_Policy *) \ + op (provider_salt, const struct ANASTASIS_CRYPTO_ProviderSaltP) \ + op (core_secret, const void) \ + op (truth_key, const struct ANASTASIS_CRYPTO_TruthKeyP) \ + op (account_pub, const struct ANASTASIS_CRYPTO_AccountPublicKeyP) \ + op (account_priv, const struct ANASTASIS_CRYPTO_AccountPrivateKeyP) \ + op (payment_secret, const struct ANASTASIS_PaymentSecretP) \ + op (truth_uuid, const struct ANASTASIS_CRYPTO_TruthUUIDP) \ + op (eks, const struct ANASTASIS_CRYPTO_EncryptedKeyShareP) \ + op (code, const char) \ + op (filename, const char) + + +/** + * Call #op on all indexed traits. + */ +#define ANASTASIS_TESTING_INDEXED_TRAITS(op) \ + op (challenges, const struct ANASTASIS_Challenge *) + + +ANASTASIS_TESTING_SIMPLE_TRAITS (ANASTASIS_TESTING_MAKE_DECL_SIMPLE_TRAIT) + +ANASTASIS_TESTING_INDEXED_TRAITS (ANASTASIS_TESTING_MAKE_DECL_INDEXED_TRAIT) /** @@ -480,7 +374,7 @@ ANASTASIS_TESTING_cmd_truth_question ( /** - * Make the "keyshare lookup" command. + * Make a "truth challenge" command. * * @param label command label * @param anastasis_url base URL of the ANASTASIS serving @@ -488,48 +382,41 @@ ANASTASIS_TESTING_cmd_truth_question ( * @param answer (response to challenge) * @param payment_ref reference to the payment request * @param upload_ref reference to upload command - * @param lookup_mode 0 for security question, 1 for - * code-based - * @param ksdd expected status + * @param http_status expected HTTP status * @return the command */ struct TALER_TESTING_Command -ANASTASIS_TESTING_cmd_keyshare_lookup ( +ANASTASIS_TESTING_cmd_truth_challenge ( const char *label, const char *anastasis_url, - const char *answer, const char *payment_ref, const char *upload_ref, - int lookup_mode, - enum ANASTASIS_KeyShareDownloadStatus ksdd); - - -/** - * Obtain a salt from @a cmd. - * - * @param cmd command to extract the salt from. - * @param index the salt's index number. - * @param[out] s set to the salt coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_salt ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_ProviderSaltP **s); + unsigned int http_status); /** - * Offer an salt. + * Make a "truth solve" command. * - * @param index the salt's index number. - * @param s the salt to offer. - * @return trait on success + * @param label command label + * @param anastasis_url base URL of the ANASTASIS serving + * the keyshare lookup request. + * @param answer (response to challenge) + * @param payment_ref reference to the payment request + * @param upload_ref reference to upload command + * @param lookup_mode 0 for security question, 1 for + * code-based + * @param http_status expected HTTP status + * @return the command */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_salt ( - unsigned int index, - const struct ANASTASIS_CRYPTO_ProviderSaltP *s); +struct TALER_TESTING_Command +ANASTASIS_TESTING_cmd_truth_solve ( + const char *label, + const char *anastasis_url, + const char *answer, + const char *payment_ref, + const char *upload_ref, + int lookup_mode, + unsigned int http_status); /** @@ -549,31 +436,6 @@ ANASTASIS_TESTING_cmd_config (const char *label, /* ********************* test truth upload ********************* */ /** - * Obtain a truth from @a cmd. - * - * @param cmd command to extract the truth from. - * @param index the index of the truth - * @param[out] t set to the truth coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_truth (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_Truth **t); - - -/** - * Offer a truth. - * - * @param index the truth's index number. - * @param t the truth to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_truth (unsigned int index, - const struct ANASTASIS_Truth *t); - -/** * Creates a sample of id_data. * * @param id_data some sample data (e.g. AHV, name, surname, ...) @@ -642,31 +504,6 @@ ANASTASIS_TESTING_cmd_truth_upload_question ( /* ********************* test policy create ********************* */ -/** - * Obtain a policy from @a cmd. - * - * @param cmd command to extract the policy from. - * @param index the index of the policy - * @param[out] p set to the policy coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_policy (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_Policy **p); - - -/** - * Offer a policy. - * - * @param index the policy's index number. - * @param p the policy to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_policy (unsigned int index, - const struct ANASTASIS_Policy *p); - /** * Make the "policy create" command. @@ -682,31 +519,6 @@ ANASTASIS_TESTING_cmd_policy_create (const char *label, /* ********************* test secret share ********************* */ -/** - * Obtain the core secret from @a cmd. - * - * @param cmd command to extract the core secret from. - * @param index the index of the core secret (usually 0) - * @param[out] s set to the core secret coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_core_secret (const struct - TALER_TESTING_Command *cmd, - unsigned int index, - const void **s); - - -/** - * Offer the core secret. - * - * @param index the core secret's index number (usually 0). - * @param s the core secret to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_core_secret (unsigned int index, - const void *s); /** * Types of options for performing the secret sharing. Used as a bitmask. @@ -740,7 +552,7 @@ enum ANASTASIS_TESTING_SecretShareOption * @param id_data ID data to generate user identifier * @param core_secret core secret to backup/recover * @param core_secret_size size of @a core_secret - * @param http_status expected HTTP status. + * @param want_status expected status. * @param sso secret share options * @param ... NULL-terminated list of policy create commands * @return the command @@ -754,7 +566,7 @@ ANASTASIS_TESTING_cmd_secret_share ( const json_t *id_data, const void *core_secret, size_t core_secret_size, - unsigned int http_status, + enum ANASTASIS_ShareStatus want_status, enum ANASTASIS_TESTING_SecretShareOption sso, ...); @@ -824,30 +636,6 @@ ANASTASIS_TESTING_cmd_recover_secret_finish ( /* ********************* test challenge answer ********************* */ -/** - * Obtain a challenge from @a cmd. - * - * @param cmd command to extract the challenge from. - * @param index the index of the challenge - * @param[out] c set to the challenge coming from @a cmd. - * @return #GNUNET_OK on success. - */ -int -ANASTASIS_TESTING_get_trait_challenge (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_Challenge **c); - -/** - * Offer a challenge. - * - * @param index the challenge index number. - * @param r the challenge to offer. - * @return trait on success - */ -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_challenge (unsigned int index, - const struct ANASTASIS_Challenge *r); - /** * Create a "challenge start" command. Suitable for the "file" @@ -866,7 +654,7 @@ ANASTASIS_TESTING_cmd_challenge_start ( const char *payment_ref, const char *challenge_ref, unsigned int challenge_index, - enum ANASTASIS_ChallengeStatus expected_cs); + enum ANASTASIS_ChallengeStartStatus expected_cs); /** @@ -890,7 +678,7 @@ ANASTASIS_TESTING_cmd_challenge_answer ( unsigned int challenge_index, const char *answer, unsigned int mode, - enum ANASTASIS_ChallengeStatus expected_cs); + enum ANASTASIS_ChallengeAnswerStatus expected_cs); #endif diff --git a/src/include/anastasis_util_lib.h b/src/include/anastasis_util_lib.h index e780d82..602e1cc 100644 --- a/src/include/anastasis_util_lib.h +++ b/src/include/anastasis_util_lib.h @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -30,6 +30,17 @@ /** + * Maximum value allowed for PINs. Limited to 10^15 < 2^52 to ensure the + * numeric value survives a conversion to float by JavaScript. + * + * NOTE: Do not change this value, we map it to a string like + * 42353-256-6521-241 and that mapping fails if the number + * does not have exactly 15 digits! + */ +#define ANASTASIS_PIN_MAX_VALUE 1000000000000000 + + +/** * Return default project data used by Anastasis. */ const struct GNUNET_OS_ProjectData * @@ -86,4 +97,26 @@ void ANASTASIS_wait_child_cancel (struct ANASTASIS_ChildWaitHandle *cwh); +/** + * Convert input string @a as into @a pin. + * + * @param as input of the form 42355-256-2262-265 + * @param[out] pin set to numeric pin + * @return false if @as is malformed + */ +bool +ANASTASIS_scan_pin (const char *as, + unsigned long long *pin); + + +/** + * Convert numeric pin to human-readable number for display. + * + * @param pin number to convert + * @return static (!) buffer with the text to show + */ +const char * +ANASTASIS_pin2s (uint64_t pin); + + #endif diff --git a/src/include/gettext.h b/src/include/gettext.h index 705487b..3929b11 100644 --- a/src/include/gettext.h +++ b/src/include/gettext.h @@ -73,8 +73,8 @@ # undef ngettext # define ngettext(Msgid1, Msgid2, N) \ ((N) == 1 \ - ? ((void) (Msgid2), (const char *) (Msgid1)) \ - : ((void) (Msgid1), (const char *) (Msgid2))) + ? ((void) (Msgid2), (const char *) (Msgid1)) \ + : ((void) (Msgid1), (const char *) (Msgid2))) # undef dngettext # define dngettext(Domainname, Msgid1, Msgid2, N) \ ((void) (Domainname), ngettext (Msgid1, Msgid2, N)) diff --git a/src/include/platform.h b/src/include/platform.h index 7667460..0fd6672 100644 --- a/src/include/platform.h +++ b/src/include/platform.h @@ -26,10 +26,10 @@ /* Include our configuration header */ #ifndef HAVE_USED_CONFIG_H -# define HAVE_USED_CONFIG_H -# ifdef HAVE_CONFIG_H -# include "anastasis_config.h" -# endif +#define HAVE_USED_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "anastasis_config.h" +#endif #endif @@ -69,8 +69,210 @@ /* Include the features available for GNU source */ #define _GNU_SOURCE -/* Include GNUnet's platform file */ -#include <gnunet/platform.h> + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef __clang__ +#undef HAVE_STATIC_ASSERT +#endif + +/** + * These may be expensive, but good for debugging... + */ +#define ALLOW_EXTRA_CHECKS GNUNET_YES + +/** + * For strptime (glibc2 needs this). + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 499 +#endif + +#ifndef _REENTRANT +#define _REENTRANT +#endif + +/* configuration options */ + +#define VERBOSE_STATS 0 + +#include <netdb.h> +#include <sys/socket.h> +#include <sys/un.h> +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif +#if HAVE_NETINET_IP_H +#include <netinet/ip.h> /* superset of previous */ +#endif +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <pwd.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <grp.h> + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <stdbool.h> +#include <errno.h> +#include <signal.h> +#include <libgen.h> +#ifdef HAVE_MALLOC_H +#include <malloc.h> /* for mallinfo on GNU */ +#endif +#include <unistd.h> /* KLB_FIX */ +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> /* KLB_FIX */ +#include <fcntl.h> +#include <math.h> +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <time.h> +#ifdef BSD +#include <net/if.h> +#endif +#if defined(BSD) && defined(__FreeBSD__) && defined(__FreeBSD_kernel__) +#include <semaphore.h> +#endif +#ifdef DARWIN +#include <dlfcn.h> +#include <semaphore.h> +#include <net/if.h> +#endif +#if defined(__linux__) || defined(GNU) +#include <net/if.h> +#endif +#ifdef SOLARIS +#include <sys/sockio.h> +#include <sys/filio.h> +#include <sys/loadavg.h> +#include <semaphore.h> +#endif +#if HAVE_UCRED_H +#include <ucred.h> +#endif +#if HAVE_SYS_UCRED_H +#include <sys/ucred.h> +#endif +#if HAVE_IFADDRS_H +#include <ifaddrs.h> +#endif +#include <errno.h> +#include <limits.h> + +#if HAVE_VFORK_H +#include <vfork.h> +#endif + +#include <ctype.h> +#if HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#if HAVE_ENDIAN_H +#include <endian.h> +#endif +#if HAVE_SYS_ENDIAN_H +#include <sys/endian.h> +#endif + +#define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_STR "/" +#define PATH_SEPARATOR ':' +#define PATH_SEPARATOR_STR ":" +#define NEWLINE "\n" + +#include <locale.h> +#include "gettext.h" +/** + * GNU gettext support macro. + */ +#define _(String) dgettext (PACKAGE, String) +#define LIBEXTRACTOR_GETTEXT_DOMAIN "libextractor" + +#include <sys/mman.h> + +/* FreeBSD_kernel is not defined on the now discontinued kFreeBSD */ +#if defined(BSD) && defined(__FreeBSD__) && defined(__FreeBSD_kernel__) +#define __BYTE_ORDER BYTE_ORDER +#define __BIG_ENDIAN BIG_ENDIAN +#endif + +#ifdef DARWIN +#define __BYTE_ORDER BYTE_ORDER +#define __BIG_ENDIAN BIG_ENDIAN +/* not available on darwin, override configure */ +#undef HAVE_STAT64 +#undef HAVE_MREMAP +#endif + +#if ! HAVE_ATOLL +long long +atoll (const char *nptr); + +#endif + +#if ENABLE_NLS +#include "langinfo.h" +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t) (-1)) +#endif + +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +/** + * AI_NUMERICSERV not defined in windows. Then we just do without. + */ +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0 +#endif + + +#if defined(__sparc__) +#define MAKE_UNALIGNED(val) ({ __typeof__((val)) __tmp; memmove (&__tmp, &(val), \ + sizeof((val))); \ + __tmp; }) +#else +#define MAKE_UNALIGNED(val) val +#endif + +/** + * The termination signal + */ +#define GNUNET_TERM_SIG SIGTERM + + +#ifndef PATH_MAX +/** + * Assumed maximum path length. + */ +#define PATH_MAX 4096 +#endif + +#if HAVE_THREAD_LOCAL_GCC +#define ANASTASIS_THREAD_LOCAL __thread +#else +#define ANASTASIS_THREAD_LOCAL +#endif + /* Do not use shortcuts for gcrypt mpi */ #define GCRYPT_NO_MPI_MACROS 1 diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 07460d4..6f71418 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -14,6 +14,7 @@ libanastasis_la_LDFLAGS = \ -no-undefined libanastasis_la_SOURCES = \ anastasis_backup.c \ + anastasis_meta.c \ anastasis_recovery.c libanastasis_la_LIBADD = \ $(top_builddir)/src/util/libanastasisutil.la \ diff --git a/src/lib/anastasis_backup.c b/src/lib/anastasis_backup.c index 6747d73..24d9643 100644 --- a/src/lib/anastasis_backup.c +++ b/src/lib/anastasis_backup.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -55,7 +55,7 @@ struct ANASTASIS_Truth /** * Server salt used to derive hash from security answer */ - struct ANASTASIS_CRYPTO_QuestionSaltP salt; + struct ANASTASIS_CRYPTO_QuestionSaltP question_salt; /** * Url of the server @@ -97,7 +97,8 @@ ANASTASIS_truth_from_json (const json_t *json) &instructions), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("mime_type", - &mime_type)), + &mime_type), + NULL), GNUNET_JSON_spec_fixed_auto ("uuid", &t->uuid), GNUNET_JSON_spec_fixed_auto ("nonce", @@ -106,8 +107,8 @@ ANASTASIS_truth_from_json (const json_t *json) &t->key_share), GNUNET_JSON_spec_fixed_auto ("truth_key", &t->truth_key), - GNUNET_JSON_spec_fixed_auto ("salt", - &t->salt), + GNUNET_JSON_spec_fixed_auto ("question_salt", + &t->question_salt), GNUNET_JSON_spec_fixed_auto ("provider_salt", &t->provider_salt), GNUNET_JSON_spec_end () @@ -141,8 +142,8 @@ ANASTASIS_truth_to_json (const struct ANASTASIS_Truth *t) &t->key_share), GNUNET_JSON_pack_data_auto ("truth_key", &t->truth_key), - GNUNET_JSON_pack_data_auto ("salt", - &t->salt), + GNUNET_JSON_pack_data_auto ("question_salt", + &t->question_salt), GNUNET_JSON_pack_data_auto ("nonce", &t->nonce), GNUNET_JSON_pack_data_auto ("provider_salt", @@ -251,7 +252,7 @@ ANASTASIS_truth_upload3 (struct GNUNET_CURL_Context *ctx, truth_data_size); ANASTASIS_CRYPTO_secure_answer_hash (answer, &t->uuid, - &t->salt, + &t->question_salt, &nt); ANASTASIS_CRYPTO_keyshare_encrypt (&t->key_share, &tu->id, @@ -313,7 +314,7 @@ ANASTASIS_truth_upload2 ( struct GNUNET_TIME_Relative pay_timeout, const struct ANASTASIS_CRYPTO_NonceP *nonce, const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, - const struct ANASTASIS_CRYPTO_QuestionSaltP *salt, + const struct ANASTASIS_CRYPTO_QuestionSaltP *question_salt, const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, const struct ANASTASIS_CRYPTO_KeyShareP *key_share, ANASTASIS_TruthCallback tc, @@ -325,13 +326,13 @@ ANASTASIS_truth_upload2 ( t->url = GNUNET_strdup (provider_url); t->type = GNUNET_strdup (type); t->instructions = (NULL != instructions) - ? GNUNET_strdup (instructions) - : NULL; + ? GNUNET_strdup (instructions) + : NULL; t->mime_type = (NULL != mime_type) - ? GNUNET_strdup (mime_type) - : NULL; + ? GNUNET_strdup (mime_type) + : NULL; t->provider_salt = *provider_salt; - t->salt = *salt; + t->question_salt = *question_salt; t->nonce = *nonce; t->uuid = *uuid; t->truth_key = *truth_key; @@ -442,7 +443,7 @@ struct ANASTASIS_Policy /** * Salt used to encrypt the master key */ - struct ANASTASIS_CRYPTO_MasterSaltP salt; + struct ANASTASIS_CRYPTO_MasterSaltP master_salt; /** * Array of truths @@ -486,8 +487,8 @@ ANASTASIS_policy_create (const struct ANASTASIS_Truth *truths[], p = GNUNET_new (struct ANASTASIS_Policy); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - &p->salt, - sizeof (p->salt)); + &p->master_salt, + sizeof (p->master_salt)); { struct ANASTASIS_CRYPTO_KeyShareP key_shares[truths_len]; @@ -495,7 +496,7 @@ ANASTASIS_policy_create (const struct ANASTASIS_Truth *truths[], key_shares[i] = truths[i]->key_share; ANASTASIS_CRYPTO_policy_key_derive (key_shares, truths_len, - &p->salt, + &p->master_salt, &p->policy_key); } p->truths = GNUNET_new_array (truths_len, @@ -541,7 +542,7 @@ struct PolicyStoreState * Server salt. Points into a truth object from which we got the * salt. */ - struct ANASTASIS_CRYPTO_ProviderSaltP server_salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; /** * The /policy POST operation handle. @@ -571,7 +572,7 @@ struct PolicyStoreState /** * When will the policy expire at the provider. */ - struct GNUNET_TIME_Absolute policy_expiration; + struct GNUNET_TIME_Timestamp policy_expiration; }; @@ -651,7 +652,7 @@ policy_store_cb (void *cls, .ss = ANASTASIS_SHARE_STATUS_PROVIDER_FAILED, .details.provider_failure.provider_url = pss->anastasis_url, .details.provider_failure.http_status = ud->http_status, - .details.provider_failure.ec = us, + .details.provider_failure.ec = ud->ec, }; ss->src (ss->src_cls, @@ -729,13 +730,13 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, size_t core_secret_size) { struct ANASTASIS_SecretShare *ss; - struct ANASTASIS_CRYPTO_EncryptedMasterKeyP - encrypted_master_keys[GNUNET_NZL (policies_len)]; - void *encrypted_core_secret; + struct ANASTASIS_CoreSecretEncryptionResult *cser; json_t *dec_policies; json_t *esc_methods; size_t recovery_document_size; char *recovery_document_str; + size_t meta_size; + void *meta; if (0 == pss_length) { @@ -755,12 +756,10 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, for (unsigned int i = 0; i < policies_len; i++) policy_keys[i] = policies[i]->policy_key; - ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys, - policies_len, - core_secret, - core_secret_size, - &encrypted_core_secret, - encrypted_master_keys); + cser = ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys, + policies_len, + core_secret, + core_secret_size); } dec_policies = json_array (); GNUNET_assert (NULL != dec_policies); @@ -780,15 +779,18 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, json_array_append_new ( dec_policies, GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("master_key", - &encrypted_master_keys[k]), + GNUNET_JSON_pack_data_varsize ("master_key", + cser->enc_master_keys[k], + cser->enc_master_key_sizes + [k]), GNUNET_JSON_pack_array_steal ("uuids", uuids), - GNUNET_JSON_pack_data_auto ("salt", - &policy->salt)))); + GNUNET_JSON_pack_data_auto ("master_salt", + &policy->master_salt)))); } esc_methods = json_array (); + GNUNET_assert (NULL != esc_methods); for (unsigned int k = 0; k < policies_len; k++) { const struct ANASTASIS_Policy *policy = policies[k]; @@ -828,8 +830,8 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, pt->instructions), GNUNET_JSON_pack_data_auto ("truth_key", &pt->truth_key), - GNUNET_JSON_pack_data_auto ("salt", - &pt->salt), + GNUNET_JSON_pack_data_auto ("question_salt", + &pt->question_salt), GNUNET_JSON_pack_data_auto ("provider_salt", &pt->provider_salt), GNUNET_JSON_pack_string ("escrow_type", @@ -855,10 +857,11 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, GNUNET_JSON_pack_array_steal ("escrow_methods", esc_methods), GNUNET_JSON_pack_data_varsize ("encrypted_core_secret", - encrypted_core_secret, - core_secret_size)); + cser->enc_core_secret, + cser->enc_core_secret_size)); GNUNET_assert (NULL != recovery_document); - GNUNET_free (encrypted_core_secret); + ANASTASIS_CRYPTO_destroy_encrypted_core_secret (cser); + cser = NULL; rd_str = json_dumps (recovery_document, JSON_COMPACT | JSON_SORT_KEYS); @@ -889,20 +892,39 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, recovery_document_str = (char *) cbuf; } + meta_size = sizeof (struct GNUNET_HashCode); + if (NULL != secret_name) + meta_size += strlen (secret_name) + 1; + meta = GNUNET_malloc (meta_size); + GNUNET_CRYPTO_hash (recovery_document_str, + recovery_document_size, + (struct GNUNET_HashCode *) meta); + if (NULL != secret_name) + memcpy (meta + sizeof (struct GNUNET_HashCode), + secret_name, + strlen (secret_name) + 1); + for (unsigned int l = 0; l < ss->pss_length; l++) { struct PolicyStoreState *pss = &ss->pss[l]; void *recovery_data; size_t recovery_data_size; struct ANASTASIS_CRYPTO_AccountPrivateKeyP anastasis_priv; + size_t enc_meta_size = 0; + void *enc_meta = NULL; pss->ss = ss; pss->anastasis_url = GNUNET_strdup (providers[l].provider_url); - pss->server_salt = providers[l].provider_salt; + pss->provider_salt = providers[l].provider_salt; pss->payment_secret = providers[l].payment_secret; ANASTASIS_CRYPTO_user_identifier_derive (id_data, - &pss->server_salt, + &pss->provider_salt, &pss->id); + ANASTASIS_CRYPTO_recovery_metadata_encrypt (&pss->id, + meta, + meta_size, + &enc_meta, + &enc_meta_size); ANASTASIS_CRYPTO_account_private_key_derive (&pss->id, &anastasis_priv); ANASTASIS_CRYPTO_recovery_document_encrypt (&pss->id, @@ -919,6 +941,8 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, &anastasis_priv, recovery_data, recovery_data_size, + enc_meta, + enc_meta_size, payment_years_requested, (! GNUNET_is_zero (&pss->payment_secret)) ? &pss->payment_secret @@ -927,14 +951,17 @@ ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx, &policy_store_cb, pss); GNUNET_free (recovery_data); + GNUNET_free (enc_meta); if (NULL == pss->pso) { GNUNET_break (0); ANASTASIS_secret_share_cancel (ss); GNUNET_free (recovery_document_str); + GNUNET_free (meta); return NULL; } } + GNUNET_free (meta); GNUNET_free (recovery_document_str); return ss; } diff --git a/src/lib/anastasis_meta.c b/src/lib/anastasis_meta.c new file mode 100644 index 0000000..ae20db5 --- /dev/null +++ b/src/lib/anastasis_meta.c @@ -0,0 +1,178 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @brief anastasis client api to get recovery document meta data + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis.h" +#include <taler/taler_json_lib.h> +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * Handle for a version check operation. + */ +struct ANASTASIS_VersionCheck +{ + /** + * Function to call with results. + */ + ANASTASIS_MetaPolicyCallback mpc; + + /** + * Closure for @e mpc. + */ + void *mpc_cls; + + /** + * Handle for the actual REST operation. + */ + struct ANASTASIS_PolicyMetaLookupOperation *plm; + + /** + * User identifier (needed to decrypt). + */ + struct ANASTASIS_CRYPTO_UserIdentifierP id; +}; + + +/** + * Function called with results from a GET /policy/$POL/meta request + * + * @param cls closure with the `struct ANASTASIS_VersionCheck *` + * @param dd the response details + */ +static void +meta_cb ( + void *cls, + const struct ANASTASIS_MetaDownloadDetails *dd) +{ + struct ANASTASIS_VersionCheck *vc = cls; + + vc->plm = NULL; + if (MHD_HTTP_OK != dd->http_status) + { + vc->mpc (vc->mpc_cls, + 0, + GNUNET_TIME_UNIT_ZERO_TS, + NULL, + NULL); + ANASTASIS_recovery_get_versions_cancel (vc); + return; + } + for (size_t i = 0; i<dd->details.ok.metas_length; i++) + { + const struct ANASTASIS_MetaDataEntry *meta + = &dd->details.ok.metas[i]; + const char *secret_name = NULL; + const struct GNUNET_HashCode *eph; + void *dec; + size_t dec_len; + + if (GNUNET_OK != + ANASTASIS_CRYPTO_recovery_metadata_decrypt ( + &vc->id, + meta->meta_data, + meta->meta_data_size, + &dec, + &dec_len)) + { + GNUNET_break_op (0); + continue; + } + if (sizeof (*eph) > dec_len) + { + GNUNET_break_op (0); + GNUNET_free (dec); + continue; + } + eph = dec; + if (sizeof (*eph) < dec_len) + { + secret_name = (const char *) &eph[1]; + dec_len -= sizeof (*eph); + if ('\0' != secret_name[dec_len - 1]) + { + GNUNET_break_op (0); + GNUNET_free (dec); + continue; + } + } + vc->mpc (vc->mpc_cls, + meta->version, + meta->server_time, + eph, + secret_name); + GNUNET_free (dec); + } + vc->mpc (vc->mpc_cls, + 0, + GNUNET_TIME_UNIT_ZERO_TS, + NULL, + NULL); + ANASTASIS_recovery_get_versions_cancel (vc); +} + + +struct ANASTASIS_VersionCheck * +ANASTASIS_recovery_get_versions ( + struct GNUNET_CURL_Context *ctx, + const json_t *id_data, + unsigned int max_version, + const char *anastasis_provider_url, + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt, + ANASTASIS_MetaPolicyCallback mpc, + void *mpc_cls) +{ + struct ANASTASIS_VersionCheck *vc; + struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; + + vc = GNUNET_new (struct ANASTASIS_VersionCheck); + vc->mpc = mpc; + vc->mpc_cls = mpc_cls; + ANASTASIS_CRYPTO_user_identifier_derive (id_data, + provider_salt, + &vc->id); + ANASTASIS_CRYPTO_account_public_key_derive (&vc->id, + &account_pub); + vc->plm = ANASTASIS_policy_meta_lookup (ctx, + anastasis_provider_url, + &account_pub, + max_version, + &meta_cb, + vc); + if (NULL == vc->plm) + { + GNUNET_break (0); + GNUNET_free (vc); + return NULL; + } + return vc; +} + + +void +ANASTASIS_recovery_get_versions_cancel (struct ANASTASIS_VersionCheck *vc) +{ + if (NULL != vc->plm) + { + ANASTASIS_policy_meta_lookup_cancel (vc->plm); + vc->plm = NULL; + } + GNUNET_free (vc); +} diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c index ac10418..41f35a5 100644 --- a/src/lib/anastasis_recovery.c +++ b/src/lib/anastasis_recovery.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -47,7 +47,7 @@ struct ANASTASIS_Challenge /** * Salt; used to derive hash from security question answers. */ - struct ANASTASIS_CRYPTO_QuestionSaltP salt; + struct ANASTASIS_CRYPTO_QuestionSaltP question_salt; /** * Provider salt; used to derive our key material from our identity @@ -63,16 +63,27 @@ struct ANASTASIS_Challenge /** * Callback which gives back the instructions and a status code of - * the request to the user when answering a challenge was initiated. + * the request to the user when answering a challenge. */ ANASTASIS_AnswerFeedback af; /** - * Closure for the challenge callback + * Closure for @e af. */ void *af_cls; /** + * Callback which gives back the instructions and a status code of + * the request to the user when initiating a challenge. + */ + ANASTASIS_ChallengeStartFeedback csf; + + /** + * Closure for @e csf. + */ + void *csf_cls; + + /** * Defines the base URL of the Anastasis provider used for the challenge. */ char *url; @@ -99,9 +110,14 @@ struct ANASTASIS_Challenge struct ANASTASIS_Recovery *recovery; /** - * keyshare lookup operation + * Handle for the /truth/$TID/challenge request. + */ + struct ANASTASIS_TruthChallengeOperation *tco; + + /** + * Handle for the /truth/$TID/solve request. */ - struct ANASTASIS_KeyShareLookupOperation *kslo; + struct ANASTASIS_TruthSolveOperation *tso; }; @@ -118,14 +134,19 @@ struct DecryptionPolicy struct ANASTASIS_DecryptionPolicy pub_details; /** - * Encrypted masterkey (encrypted with the policy key). + * Encrypted master key (encrypted with the policy key). */ - struct ANASTASIS_CRYPTO_EncryptedMasterKeyP emk; + void *emk; + + /** + * Size of the encrypted master key. + */ + size_t emk_size; /** * Salt used to decrypt master key. */ - struct ANASTASIS_CRYPTO_MasterSaltP salt; + struct ANASTASIS_CRYPTO_MasterSaltP master_salt; }; @@ -233,161 +254,140 @@ struct ANASTASIS_Recovery /** - * Function called with the results of a #ANASTASIS_keyshare_lookup(). + * Function called with the results of a #ANASTASIS_challenge_start(). * * @param cls closure * @param dd details about the lookup operation */ static void -keyshare_lookup_cb (void *cls, - const struct ANASTASIS_KeyShareDownloadDetails *dd) +truth_challenge_cb (void *cls, + const struct ANASTASIS_TruthChallengeDetails *tcd) { struct ANASTASIS_Challenge *c = cls; - struct ANASTASIS_Recovery *recovery = c->recovery; - struct ANASTASIS_CRYPTO_UserIdentifierP id; - struct DecryptionPolicy *rdps; - - c->kslo = NULL; - switch (dd->status) + struct ANASTASIS_ChallengeStartResponse csr = { + .challenge = c, + .ec = tcd->ec, + .http_status = tcd->http_status + }; + + c->tco = NULL; + switch (tcd->http_status) { - case ANASTASIS_KSD_SUCCESS: - break; - case ANASTASIS_KSD_PAYMENT_REQUIRED: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED, - .challenge = c, - .details.payment_required.taler_pay_uri - = dd->details.payment_required.taler_pay_uri, - .details.payment_required.payment_secret - = dd->details.payment_required.payment_secret - }; - - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_INVALID_ANSWER: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS, - .challenge = c, - .details.open_challenge.body - = dd->details.open_challenge.body, - .details.open_challenge.content_type - = dd->details.open_challenge.content_type, - .details.open_challenge.body_size - = dd->details.open_challenge.body_size, - .details.open_challenge.http_status - = dd->details.open_challenge.http_status - }; - - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION, - .challenge = c, - .details.redirect_url - = dd->details.redirect_url - }; - - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_TRUTH_UNKNOWN: + case MHD_HTTP_OK: + switch (tcd->details.success.cs) { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN, - .challenge = c - }; - - c->af (c->af_cls, - &csr); - return; + case ANASTASIS_CS_FILE_WRITTEN: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED; + csr.details.tan_filename + = tcd->details.success.details.challenge_filename; + break; + case ANASTASIS_CS_TAN_SENT: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED; + csr.details.tan_address_hint + = tcd->details.success.details.tan_address_hint; + break; + case ANASTASIS_CS_TAN_ALREADY_SENT: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT; + break; + case ANASTASIS_CS_WIRE_FUNDS: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED; + csr.details.bank_transfer_required + = tcd->details.success.details.wire_funds; + break; } - case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, - .challenge = c - }; + break; + case MHD_HTTP_PAYMENT_REQUIRED: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED; + csr.details.payment_required.taler_pay_uri + = tcd->details.payment_required.payment_request; + csr.details.payment_required.payment_secret + = tcd->details.payment_required.ps; + break; + case MHD_HTTP_NOT_FOUND: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN; + break; + default: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE; + break; + } + c->csf (c->csf_cls, + &csr); +} - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_SERVER_ERROR: - case ANASTASIS_KSD_CLIENT_FAILURE: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE, - .challenge = c, - .details.server_failure.ec - = dd->details.server_failure.ec, - .details.server_failure.http_status - = dd->details.server_failure.http_status - }; - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT, - .challenge = c, - .details.server_failure.ec - = dd->details.server_failure.ec, - .details.server_failure.http_status - = dd->details.server_failure.http_status - }; +/** + * Function called with the results of a #ANASTASIS_truth_solve(). + * + * @param cls closure + * @param tsr details about the solution response + */ +static void +truth_solve_cb (void *cls, + const struct ANASTASIS_TruthSolveReply *tsr) +{ + struct ANASTASIS_Challenge *c = cls; + struct ANASTASIS_Recovery *recovery = c->recovery; + struct ANASTASIS_CRYPTO_UserIdentifierP id; + struct DecryptionPolicy *rdps; + struct ANASTASIS_ChallengeAnswerResponse csr = { + .challenge = c, + .ec = tsr->ec, + .http_status = tsr->http_status + }; - c->ci.async = true; - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS, - .challenge = c, - .details.external_challenge = dd->details.external_challenge - }; - c->af (c->af_cls, - &csr); - return; - } + c->tso = NULL; + switch (tsr->http_status) + { + case MHD_HTTP_OK: + break; + case MHD_HTTP_PAYMENT_REQUIRED: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED; + csr.details.payment_required.taler_pay_uri + = tsr->details.payment_required.payment_request; + csr.details.payment_required.payment_secret + = tsr->details.payment_required.ps; + c->af (c->af_cls, + &csr); + return; + case MHD_HTTP_FORBIDDEN: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER; + c->af (c->af_cls, + &csr); + return; + case MHD_HTTP_NOT_FOUND: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN; + c->af (c->af_cls, + &csr); + return; + case MHD_HTTP_TOO_MANY_REQUESTS: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED; + csr.details.rate_limit_exceeded.request_limit + = tsr->details.too_many_requests.request_limit; + csr.details.rate_limit_exceeded.request_frequency + = tsr->details.too_many_requests.request_frequency; + c->af (c->af_cls, + &csr); + return; + default: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE; + c->af (c->af_cls, + &csr); + return; } - GNUNET_assert (NULL != dd); ANASTASIS_CRYPTO_user_identifier_derive (recovery->id_data, &c->provider_salt, &id); - ANASTASIS_CRYPTO_keyshare_decrypt (&dd->details.eks, + ANASTASIS_CRYPTO_keyshare_decrypt (&tsr->details.success.eks, &id, c->answer, &c->key_share); recovery->solved_challenges[recovery->solved_challenge_pos++] = c; - - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_SOLVED, - .challenge = c - }; - - c->ci.solved = true; - c->af (c->af_cls, - &csr); - } - + c->ci.solved = true; + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED; + c->af (c->af_cls, + &csr); /* Check if there is a policy for which all challenges have been satisfied, if so, store it in 'rdps'. */ @@ -437,9 +437,12 @@ keyshare_lookup_cb (void *cls, key_shares[l] = recovery->solved_challenges[m]->key_share; ANASTASIS_CRYPTO_policy_key_derive (key_shares, rdps->pub_details.challenges_length, - &rdps->salt, + &rdps->master_salt, &policy_key); - ANASTASIS_CRYPTO_core_secret_recover (&rdps->emk, + GNUNET_assert (NULL != rdps->emk); + GNUNET_assert (rdps->emk_size > 0); + ANASTASIS_CRYPTO_core_secret_recover (rdps->emk, + rdps->emk_size, &policy_key, recovery->enc_core_secret, recovery->enc_core_secret_size, @@ -462,36 +465,70 @@ ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge) } -int +enum GNUNET_GenericReturnValue ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, - struct GNUNET_TIME_Relative timeout, - const struct GNUNET_HashCode *hashed_answer, - ANASTASIS_AnswerFeedback af, - void *af_cls) + ANASTASIS_ChallengeStartFeedback csf, + void *csf_cls) +{ + if (c->ci.solved) + { + GNUNET_break (0); + return GNUNET_NO; /* already solved */ + } + if (NULL != c->tco) + { + GNUNET_break (0); + return GNUNET_NO; /* already solving */ + } + c->csf = csf; + c->csf_cls = csf_cls; + c->tco = ANASTASIS_truth_challenge (c->recovery->ctx, + c->url, + &c->ci.uuid, + &c->truth_key, + psp, + &truth_challenge_cb, + c); + if (NULL == c->tco) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +ANASTASIS_challenge_answer3 (struct ANASTASIS_Challenge *c, + const struct ANASTASIS_PaymentSecretP *psp, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_HashCode *hashed_answer, + ANASTASIS_AnswerFeedback af, + void *af_cls) { if (c->ci.solved) { GNUNET_break (0); return GNUNET_NO; /* already solved */ } - if (NULL != c->kslo) + if (NULL != c->tso) { GNUNET_break (0); return GNUNET_NO; /* already solving */ } c->af = af; c->af_cls = af_cls; - c->kslo = ANASTASIS_keyshare_lookup (c->recovery->ctx, - c->url, - &c->ci.uuid, - &c->truth_key, - psp, - timeout, - hashed_answer, - &keyshare_lookup_cb, - c); - if (NULL == c->kslo) + c->tso = ANASTASIS_truth_solve (c->recovery->ctx, + c->url, + &c->ci.uuid, + &c->truth_key, + psp, + timeout, + hashed_answer, + &truth_solve_cb, + c); + if (NULL == c->tso) { GNUNET_break (0); return GNUNET_SYSERR; @@ -500,7 +537,7 @@ ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c, } -int +enum GNUNET_GenericReturnValue ANASTASIS_challenge_answer ( struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, @@ -512,21 +549,24 @@ ANASTASIS_challenge_answer ( struct GNUNET_HashCode hashed_answer; GNUNET_free (c->answer); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Answer to challenge is `%s'\n", + answer_str); c->answer = GNUNET_strdup (answer_str); ANASTASIS_CRYPTO_secure_answer_hash (answer_str, &c->ci.uuid, - &c->salt, + &c->question_salt, &hashed_answer); - return ANASTASIS_challenge_start (c, - psp, - timeout, - &hashed_answer, - af, - af_cls); + return ANASTASIS_challenge_answer3 (c, + psp, + timeout, + &hashed_answer, + af, + af_cls); } -int +enum GNUNET_GenericReturnValue ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, struct GNUNET_TIME_Relative timeout, @@ -536,27 +576,33 @@ ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c, { struct GNUNET_HashCode answer_s; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Answer to challenge is %llu\n", + (unsigned long long) answer); ANASTASIS_hash_answer (answer, &answer_s); - return ANASTASIS_challenge_start (c, - psp, - timeout, - &answer_s, - af, - af_cls); + return ANASTASIS_challenge_answer3 (c, + psp, + timeout, + &answer_s, + af, + af_cls); } void ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c) { - if (NULL == c->kslo) + if (NULL != c->tso) { - GNUNET_break (0); - return; + ANASTASIS_truth_solve_cancel (c->tso); + c->tso = NULL; + } + if (NULL != c->tco) + { + ANASTASIS_truth_challenge_cancel (c->tco); + c->tco = NULL; } - ANASTASIS_keyshare_lookup_cancel (c->kslo); - c->kslo = NULL; c->af = NULL; c->af_cls = NULL; } @@ -566,23 +612,22 @@ ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c) * Function called with the results of a #ANASTASIS_policy_lookup() * * @param cls closure - * @param http_status HTTp status code. * @param dd details about the lookup operation */ static void policy_lookup_cb (void *cls, - unsigned int http_status, const struct ANASTASIS_DownloadDetails *dd) { struct ANASTASIS_Recovery *r = cls; void *plaintext; size_t size_plaintext; json_error_t json_error; - json_t *dec_policies; - json_t *esc_methods; + const json_t *dec_policies; + const json_t *esc_methods; + json_t *recovery_document; r->plo = NULL; - switch (http_status) + switch (dd->http_status) { case MHD_HTTP_OK: break; @@ -614,7 +659,7 @@ policy_lookup_cb (void *cls, default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u in %s:%u\n", - http_status, + dd->http_status, __FILE__, __LINE__); r->csc (r->csc_cls, @@ -624,7 +669,7 @@ policy_lookup_cb (void *cls, ANASTASIS_recovery_abort (r); return; } - if (NULL == dd->policy) + if (NULL == dd->details.ok.policy) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No recovery data available"); @@ -636,8 +681,8 @@ policy_lookup_cb (void *cls, return; } ANASTASIS_CRYPTO_recovery_document_decrypt (&r->id, - dd->policy, - dd->policy_size, + dd->details.ok.policy, + dd->details.ok.policy_size, &plaintext, &size_plaintext); if (size_plaintext < sizeof (uint32_t)) @@ -652,7 +697,6 @@ policy_lookup_cb (void *cls, return; } { - json_t *recovery_document; uint32_t be_size; uLongf pt_size; char *pt; @@ -715,13 +759,14 @@ policy_lookup_cb (void *cls, { const char *secret_name = NULL; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("policies", - &dec_policies), - GNUNET_JSON_spec_json ("escrow_methods", - &esc_methods), + GNUNET_JSON_spec_array_const ("policies", + &dec_policies), + GNUNET_JSON_spec_array_const ("escrow_methods", + &esc_methods), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("secret_name", - &secret_name)), + &secret_name), + NULL), GNUNET_JSON_spec_varsize ("encrypted_core_secret", &r->enc_core_secret, &r->enc_core_secret_size), @@ -737,13 +782,11 @@ policy_lookup_cb (void *cls, json_dumpf (recovery_document, stderr, 0); - json_decref (recovery_document); r->csc (r->csc_cls, ANASTASIS_RS_POLICY_MALFORMED_JSON, NULL, 0); - ANASTASIS_recovery_abort (r); - return; + goto cleanup; } if (NULL != secret_name) { @@ -752,22 +795,39 @@ policy_lookup_cb (void *cls, r->ri.secret_name = r->secret_name; } } - json_decref (recovery_document); } - r->ri.version = dd->version; - r->ri.cs_len = json_array_size (esc_methods); - r->ri.dps_len = json_array_size (dec_policies); - r->ri.dps = GNUNET_new_array (r->ri.dps_len, - struct ANASTASIS_DecryptionPolicy *); - r->dps = GNUNET_new_array (r->ri.dps_len, - struct DecryptionPolicy); - r->solved_challenges = GNUNET_new_array (r->ri.cs_len, - struct ANASTASIS_Challenge *); - r->ri.cs = GNUNET_new_array (r->ri.cs_len, - struct ANASTASIS_Challenge *); - r->cs = GNUNET_new_array (r->ri.cs_len, - struct ANASTASIS_Challenge); + if ( (json_array_size (esc_methods) > UINT_MAX) || + (json_array_size (dec_policies) > UINT_MAX) ) + { + GNUNET_break_op (0); + r->csc (r->csc_cls, + ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG, + NULL, + 0); + goto cleanup; + } + + r->ri.version = dd->details.ok.version; + r->ri.cs_len + = (unsigned int) json_array_size (esc_methods); + r->ri.dps_len + = (unsigned int) json_array_size (dec_policies); + r->ri.dps + = GNUNET_new_array (r->ri.dps_len, + struct ANASTASIS_DecryptionPolicy *); + r->dps + = GNUNET_new_array (r->ri.dps_len, + struct DecryptionPolicy); + r->solved_challenges + = GNUNET_new_array (r->ri.cs_len, + struct ANASTASIS_Challenge *); + r->ri.cs + = GNUNET_new_array (r->ri.cs_len, + struct ANASTASIS_Challenge *); + r->cs + = GNUNET_new_array (r->ri.cs_len, + struct ANASTASIS_Challenge); for (unsigned int i = 0; i < r->ri.cs_len; i++) { struct ANASTASIS_Challenge *cs = &r->cs[i]; @@ -777,14 +837,14 @@ policy_lookup_cb (void *cls, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("uuid", &cs->ci.uuid), - GNUNET_JSON_spec_string ("url", + TALER_JSON_spec_web_url ("url", &url), GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_fixed_auto ("truth_key", &cs->truth_key), - GNUNET_JSON_spec_fixed_auto ("salt", - &cs->salt), + GNUNET_JSON_spec_fixed_auto ("question_salt", + &cs->question_salt), GNUNET_JSON_spec_fixed_auto ("provider_salt", &cs->provider_salt), GNUNET_JSON_spec_string ("escrow_type", @@ -801,14 +861,11 @@ policy_lookup_cb (void *cls, NULL, NULL)) { GNUNET_break_op (0); - json_decref (esc_methods); - json_decref (dec_policies); r->csc (r->csc_cls, ANASTASIS_RS_POLICY_MALFORMED_JSON, NULL, 0); - ANASTASIS_recovery_abort (r); - return; + goto cleanup; } cs->url = GNUNET_strdup (url); cs->type = GNUNET_strdup (escrow_type); @@ -817,44 +874,53 @@ policy_lookup_cb (void *cls, cs->instructions = GNUNET_strdup (instructions); cs->ci.instructions = cs->instructions; } - json_decref (esc_methods); for (unsigned int j = 0; j < r->ri.dps_len; j++) { struct DecryptionPolicy *dp = &r->dps[j]; - json_t *uuids = NULL; + const json_t *uuids; json_t *uuid; size_t n_index; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("master_key", - &dp->emk), - GNUNET_JSON_spec_fixed_auto ("salt", - &dp->salt), - GNUNET_JSON_spec_json ("uuids", - &uuids), + GNUNET_JSON_spec_varsize ("master_key", + &dp->emk, + &dp->emk_size), + GNUNET_JSON_spec_fixed_auto ("master_salt", + &dp->master_salt), + GNUNET_JSON_spec_array_const ("uuids", + &uuids), GNUNET_JSON_spec_end () }; r->ri.dps[j] = &r->dps[j].pub_details; - if ( (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (dec_policies, - j), - spec, - NULL, NULL)) || - (! json_is_array (uuids)) ) + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (dec_policies, + j), + spec, + NULL, NULL)) { GNUNET_break_op (0); - json_decref (uuids); - json_decref (dec_policies); r->csc (r->csc_cls, ANASTASIS_RS_POLICY_MALFORMED_JSON, NULL, 0); - ANASTASIS_recovery_abort (r); - return; + goto cleanup; } - dp->pub_details.challenges_length = json_array_size (uuids); + GNUNET_assert (NULL != dp->emk); + GNUNET_assert (dp->emk_size > 0); + + if (json_array_size (uuids) > UINT_MAX) + { + GNUNET_break_op (0); + r->csc (r->csc_cls, + ANASTASIS_RS_POLICY_MALFORMED_JSON, + NULL, + 0); + goto cleanup; + } + dp->pub_details.challenges_length + = (unsigned int) json_array_size (uuids); dp->pub_details.challenges = GNUNET_new_array (dp->pub_details.challenges_length, struct ANASTASIS_Challenge *); @@ -873,14 +939,11 @@ policy_lookup_cb (void *cls, sizeof (uuid))) ) { GNUNET_break_op (0); - json_decref (dec_policies); - json_decref (uuids); r->csc (r->csc_cls, ANASTASIS_RS_POLICY_MALFORMED_JSON, NULL, 0); - ANASTASIS_recovery_abort (r); - return; + goto cleanup; } for (unsigned int i = 0; i<r->ri.cs_len; i++) { @@ -895,21 +958,21 @@ policy_lookup_cb (void *cls, if (! found) { GNUNET_break_op (0); - json_decref (dec_policies); - json_decref (uuids); r->csc (r->csc_cls, ANASTASIS_RS_POLICY_MALFORMED_JSON, NULL, 0); - ANASTASIS_recovery_abort (r); - return; + goto cleanup; } } - json_decref (uuids); } - json_decref (dec_policies); r->pc (r->pc_cls, &r->ri); + json_decref (recovery_document); + return; +cleanup: + ANASTASIS_recovery_abort (r); + json_decref (recovery_document); } @@ -997,11 +1060,13 @@ ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r) json_array_append_new (c_arr, cs)); } + GNUNET_assert (NULL != dp->emk); dps = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("emk", - &dp->emk), - GNUNET_JSON_pack_data_auto ("salt", - &dp->salt), + GNUNET_JSON_pack_data_varsize ("encrypted_master_key", + dp->emk, + dp->emk_size), + GNUNET_JSON_pack_data_auto ("master_salt", + &dp->master_salt), GNUNET_JSON_pack_array_steal ("challenges", c_arr)); GNUNET_assert (0 == @@ -1018,10 +1083,12 @@ ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r) cs = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("uuid", &c->ci.uuid), + GNUNET_JSON_pack_string ("uuid-display", + ANASTASIS_CRYPTO_uuid2s (&c->ci.uuid)), GNUNET_JSON_pack_data_auto ("truth_key", &c->truth_key), - GNUNET_JSON_pack_data_auto ("salt", - &c->salt), + GNUNET_JSON_pack_data_auto ("question_salt", + &c->question_salt), GNUNET_JSON_pack_data_auto ("provider_salt", &c->provider_salt), GNUNET_JSON_pack_allow_null ( @@ -1048,9 +1115,9 @@ ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r) return GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("id", &r->id), - GNUNET_JSON_pack_array_steal ("dps", + GNUNET_JSON_pack_array_steal ("decryption_policies", dps_arr), - GNUNET_JSON_pack_array_steal ("cs", + GNUNET_JSON_pack_array_steal ("challenges", cs_arr), GNUNET_JSON_pack_uint64 ("version", r->ri.version), @@ -1061,34 +1128,43 @@ ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r) GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("secret_name", r->secret_name)), - GNUNET_JSON_pack_data_varsize ("core_secret", + GNUNET_JSON_pack_data_varsize ("encrypted_core_secret", r->enc_core_secret, r->enc_core_secret_size)); } /** - * Parse the @a cs_array and update @a r accordingly + * Parse the @a cs_array with information about + * the various challenges and their solution state + * and update @a r accordingly * * @param[in,out] r recovery information to update * @param cs_arr serialized data to parse * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue parse_cs_array (struct ANASTASIS_Recovery *r, - json_t *cs_arr) + const json_t *cs_arr) { json_t *cs; - unsigned int n_index; + size_t n_index; if (! json_is_array (cs_arr)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - r->ri.cs_len = json_array_size (cs_arr); - r->solved_challenges = GNUNET_new_array (r->ri.cs_len, - struct ANASTASIS_Challenge *); + if (json_array_size (cs_arr) > UINT_MAX) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + r->ri.cs_len + = (unsigned int) json_array_size (cs_arr); + r->solved_challenges + = GNUNET_new_array (r->ri.cs_len, + struct ANASTASIS_Challenge *); r->ri.cs = GNUNET_new_array (r->ri.cs_len, struct ANASTASIS_Challenge *); r->cs = GNUNET_new_array (r->ri.cs_len, @@ -1099,27 +1175,30 @@ parse_cs_array (struct ANASTASIS_Recovery *r, const char *instructions; const char *url; const char *escrow_type; + bool no_key_share; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("uuid", &c->ci.uuid), - GNUNET_JSON_spec_string ("url", + TALER_JSON_spec_web_url ("url", &url), GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_fixed_auto ("truth_key", &c->truth_key), - GNUNET_JSON_spec_fixed_auto ("salt", - &c->salt), + GNUNET_JSON_spec_fixed_auto ("question_salt", + &c->question_salt), GNUNET_JSON_spec_fixed_auto ("provider_salt", &c->provider_salt), GNUNET_JSON_spec_string ("type", &escrow_type), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("async", - &c->ci.async)), + &c->ci.async), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("key_share", - &c->key_share)), + &c->key_share), + &no_key_share), GNUNET_JSON_spec_end () }; @@ -1139,44 +1218,43 @@ parse_cs_array (struct ANASTASIS_Recovery *r, c->instructions = GNUNET_strdup (instructions); c->ci.instructions = c->instructions; c->ci.provider_url = c->url; + if (! no_key_share) { - json_t *ks; - - ks = json_object_get (cs, - "key_share"); - if ( (NULL != ks) && - (! json_is_null (ks)) ) - { - c->ci.solved = true; - r->solved_challenges[r->solved_challenge_pos++] = c; - } + c->ci.solved = true; + r->solved_challenges[r->solved_challenge_pos++] = c; } } - return GNUNET_OK; } /** - * Parse the @a dps_array and update @a r accordingly + * Parse the @a dps_array with our decryption policies + * and update @a r accordingly * * @param[in,out] r recovery information to update * @param dps_arr serialized data to parse * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue parse_dps_array (struct ANASTASIS_Recovery *r, - json_t *dps_arr) + const json_t *dps_arr) { json_t *dps; - unsigned int n_index; + size_t n_index; if (! json_is_array (dps_arr)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - r->ri.dps_len = json_array_size (dps_arr); + if (json_array_size (dps_arr) > UINT_MAX) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + r->ri.dps_len + = (unsigned int) json_array_size (dps_arr); r->dps = GNUNET_new_array (r->ri.dps_len, struct DecryptionPolicy); r->ri.dps = GNUNET_new_array (r->ri.dps_len, @@ -1185,14 +1263,15 @@ parse_dps_array (struct ANASTASIS_Recovery *r, json_array_foreach (dps_arr, n_index, dps) { struct DecryptionPolicy *dp = &r->dps[n_index]; - json_t *challenges; + const json_t *challenges; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("emk", - &dp->emk), - GNUNET_JSON_spec_fixed_auto ("salt", - &dp->salt), - GNUNET_JSON_spec_json ("challenges", - &challenges), + GNUNET_JSON_spec_varsize ("encrypted_master_key", + &dp->emk, + &dp->emk_size), + GNUNET_JSON_spec_fixed_auto ("master_salt", + &dp->master_salt), + GNUNET_JSON_spec_array_const ("challenges", + &challenges), GNUNET_JSON_spec_end () }; const char *err_json_name; @@ -1213,20 +1292,22 @@ parse_dps_array (struct ANASTASIS_Recovery *r, JSON_INDENT (2)); return GNUNET_SYSERR; } - if (! json_is_array (challenges)) + GNUNET_assert (NULL != dp->emk); + GNUNET_assert (dp->emk_size > 0); + if (json_array_size (challenges) > UINT_MAX) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - dp->pub_details.challenges_length = json_array_size (challenges); - dp->pub_details.challenges = GNUNET_new_array ( - dp->pub_details.challenges_length, - struct ANASTASIS_Challenge *); + dp->pub_details.challenges_length + = (unsigned int) json_array_size (challenges); + dp->pub_details.challenges + = GNUNET_new_array (dp->pub_details.challenges_length, + struct ANASTASIS_Challenge *); { json_t *challenge; - unsigned int c_index; + size_t c_index; json_array_foreach (challenges, c_index, challenge) { struct ANASTASIS_CRYPTO_TruthUUIDP uuid; @@ -1263,7 +1344,7 @@ parse_dps_array (struct ANASTASIS_Recovery *r, } } } - GNUNET_JSON_parse_free (spec); + /* Do NOT free the spec: we are still using dp->ems. */ } return GNUNET_OK; } @@ -1305,9 +1386,9 @@ ANASTASIS_recovery_deserialize (struct GNUNET_CURL_Context *ctx, const char *err_json_name; unsigned int err_line; uint32_t version; - json_t *dps_arr; - json_t *cs_arr; - json_t *id_data; + const json_t *dps_arr; + const json_t *cs_arr; + const json_t *id_data; const char *provider_url; const char *secret_name; void *ecs; @@ -1315,20 +1396,21 @@ ANASTASIS_recovery_deserialize (struct GNUNET_CURL_Context *ctx, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("id", &r->id), - GNUNET_JSON_spec_string ("provider_url", + TALER_JSON_spec_web_url ("provider_url", &provider_url), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("secret_name", - &secret_name)), + &secret_name), + NULL), GNUNET_JSON_spec_uint32 ("version", &version), - GNUNET_JSON_spec_json ("dps", - &dps_arr), - GNUNET_JSON_spec_json ("cs", - &cs_arr), - GNUNET_JSON_spec_json ("id_data", - &id_data), - GNUNET_JSON_spec_varsize ("core_secret", + GNUNET_JSON_spec_array_const ("decryption_policies", + &dps_arr), + GNUNET_JSON_spec_array_const ("challenges", + &cs_arr), + GNUNET_JSON_spec_object_const ("id_data", + &id_data), + GNUNET_JSON_spec_varsize ("encrypted_core_secret", &ecs, &ecs_size), GNUNET_JSON_spec_end () @@ -1361,7 +1443,7 @@ ANASTASIS_recovery_deserialize (struct GNUNET_CURL_Context *ctx, GNUNET_JSON_parse_free (spec); return NULL; } - r->id_data = json_incref (id_data); + r->id_data = json_incref ((json_t *) id_data); r->provider_url = GNUNET_strdup (provider_url); if (NULL != secret_name) r->secret_name = GNUNET_strdup (secret_name); @@ -1428,16 +1510,19 @@ ANASTASIS_recovery_abort (struct ANASTASIS_Recovery *r) } GNUNET_free (r->solved_challenges); for (unsigned int j = 0; j < r->ri.dps_len; j++) + { GNUNET_free (r->dps[j].pub_details.challenges); + GNUNET_free (r->dps[j].emk); + } GNUNET_free (r->ri.dps); for (unsigned int i = 0; i < r->ri.cs_len; i++) { - struct ANASTASIS_Challenge *cs = r->ri.cs[i]; + struct ANASTASIS_Challenge *cs = &r->cs[i]; - if (NULL != cs->kslo) + if (NULL != cs->tso) { - ANASTASIS_keyshare_lookup_cancel (cs->kslo); - cs->kslo = NULL; + ANASTASIS_truth_solve_cancel (cs->tso); + cs->tso = NULL; } GNUNET_free (cs->url); GNUNET_free (cs->type); diff --git a/src/reducer/Makefile.am b/src/reducer/Makefile.am index 5cbe6f7..1536d21 100644 --- a/src/reducer/Makefile.am +++ b/src/reducer/Makefile.am @@ -15,6 +15,8 @@ libanastasisredux_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined libanastasisredux_la_SOURCES = \ + anastasis_api_discovery.c \ + anastasis_api_providers.c \ anastasis_api_redux.c anastasis_api_redux.h \ anastasis_api_recovery_redux.c \ anastasis_api_backup_redux.c \ @@ -22,8 +24,11 @@ libanastasisredux_la_SOURCES = \ validation_CZ_BN.c \ validation_DE_SVN.c \ validation_DE_TIN.c \ + validation_ES_DNI.c \ + validation_FR_INSEE.c \ validation_IN_AADHAR.c \ validation_IT_CF.c \ + validation_NL_BSN.c \ validation_XX_SQUARE.c \ validation_XY_PRIME.c libanastasisredux_la_LIBADD = \ diff --git a/src/reducer/anastasis_api_backup_redux.c b/src/reducer/anastasis_api_backup_redux.c index cfef852..6ca6de7 100644 --- a/src/reducer/anastasis_api_backup_redux.c +++ b/src/reducer/anastasis_api_backup_redux.c @@ -1,16 +1,16 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020-2023 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -32,7 +32,7 @@ * anastasis-httpd.h. */ #define ANASTASIS_FREE_STORAGE GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_YEARS, 5) + GNUNET_TIME_UNIT_YEARS, 5) /** * CPU limiter: do not evaluate more than 16k @@ -131,7 +131,7 @@ ANASTASIS_backup_state_from_string_ (const char *state_string) if (0 == strcmp (state_string, backup_strings[i])) return i; - return ANASTASIS_BACKUP_STATE_ERROR; + return ANASTASIS_BACKUP_STATE_INVALID; } @@ -177,11 +177,78 @@ json_t * ANASTASIS_backup_start (const struct GNUNET_CONFIGURATION_Handle *cfg) { json_t *initial_state; + const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer (); + + if (NULL != external_reducer) + { + int pipefd_stdout[2]; + pid_t pid = 0; + int status; + FILE *reducer_stdout; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Using external reducer '%s' for backup start status\n", + external_reducer); + + GNUNET_assert (0 == pipe (pipefd_stdout)); + pid = fork (); + if (pid == 0) + { + close (pipefd_stdout[0]); + dup2 (pipefd_stdout[1], STDOUT_FILENO); + execlp (external_reducer, + external_reducer, + "-b", + NULL); + GNUNET_assert (0); + } + + close (pipefd_stdout[1]); + reducer_stdout = fdopen (pipefd_stdout[0], + "r"); + { + json_error_t err; + + initial_state = json_loadf (reducer_stdout, + 0, + &err); + + if (NULL == initial_state) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "External reducer did not output valid JSON: %s:%d:%d %s\n", + err.source, + err.line, + err.column, + err.text); + GNUNET_assert (0 == fclose (reducer_stdout)); + waitpid (pid, &status, 0); + return NULL; + } + } + + GNUNET_assert (NULL != initial_state); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Waiting for external reducer to terminate.\n"); + GNUNET_assert (0 == fclose (reducer_stdout)); + reducer_stdout = NULL; + waitpid (pid, &status, 0); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "External reducer finished with exit status '%d'\n", + status); + return initial_state; + } (void) cfg; initial_state = ANASTASIS_REDUX_load_continents_ (); if (NULL == initial_state) return NULL; + GNUNET_assert ( + 0 == + json_object_set_new (initial_state, + "reducer_type", + json_string ("backup"))); set_state (initial_state, ANASTASIS_BACKUP_STATE_CONTINENT_SELECTING); return initial_state; @@ -286,22 +353,30 @@ add_authentication (json_t *state, json_object_foreach (auth_providers, url, details) { - json_t *methods; + const json_t *methods = NULL; json_t *method; size_t index; - uint32_t size_limit_in_mb; + uint32_t size_limit_in_mb = 0; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", - &size_limit_in_mb), - GNUNET_JSON_spec_json ("methods", - &methods), + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", + &size_limit_in_mb), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("methods", + &methods), + NULL), GNUNET_JSON_spec_end () }; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (details, - "http_status"))) - continue; /* skip providers that are down */ if (GNUNET_OK != GNUNET_JSON_parse (details, ispec, @@ -310,6 +385,17 @@ add_authentication (json_t *state, GNUNET_break (0); continue; } + if (0 != strcmp (status, + "ok")) + continue; + if (MHD_HTTP_OK != http_status) + continue; /* skip providers that are down */ + if ( (NULL == methods) || + (0 == size_limit_in_mb) ) + { + GNUNET_break (0); + continue; + } json_array_foreach (methods, index, method) { const char *type; @@ -325,7 +411,6 @@ add_authentication (json_t *state, break; } } - GNUNET_JSON_parse_free (ispec); if (! challenge_size_ok (size_limit_in_mb, challenge_size)) { @@ -681,17 +766,20 @@ free_costs (struct Costs *costs) * Check if providers @a p1 and @a p2 have equivalent * methods and cost structures. * + * @param pb policy builder with list of providers + * @param p1 name of provider to compare + * @param p2 name of provider to compare * @return true if the providers are fully equivalent */ static bool -equiv_provider (struct PolicyBuilder *pb, +equiv_provider (const struct PolicyBuilder *pb, const char *p1, const char *p2) { - json_t *j1; - json_t *j2; - json_t *m1; - json_t *m2; + const json_t *j1; + const json_t *j2; + const json_t *m1; + const json_t *m2; struct TALER_Amount uc1; struct TALER_Amount uc2; @@ -708,8 +796,8 @@ equiv_provider (struct PolicyBuilder *pb, { struct GNUNET_JSON_Specification s1[] = { - GNUNET_JSON_spec_json ("methods", - &m1), + GNUNET_JSON_spec_array_const ("methods", + &m1), TALER_JSON_spec_amount_any ("truth_upload_fee", &uc1), GNUNET_JSON_spec_end () @@ -727,8 +815,8 @@ equiv_provider (struct PolicyBuilder *pb, { struct GNUNET_JSON_Specification s2[] = { - GNUNET_JSON_spec_json ("methods", - &m2), + GNUNET_JSON_spec_array_const ("methods", + &m2), TALER_JSON_spec_amount_any ("truth_upload_fee", &uc2), GNUNET_JSON_spec_end () @@ -847,7 +935,7 @@ eval_provider_selection (struct PolicyBuilder *pb, pb->m_idx[i]); const json_t *provider_cfg = json_object_get (pb->providers, prov_sel[i]); - json_t *provider_methods; + const json_t *provider_methods; const char *method_type; json_t *md; size_t index; @@ -857,8 +945,8 @@ eval_provider_selection (struct PolicyBuilder *pb, struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", &size_limit_in_mb), - GNUNET_JSON_spec_json ("methods", - &provider_methods), + GNUNET_JSON_spec_array_const ("methods", + &provider_methods), TALER_JSON_spec_amount_any ("truth_upload_fee", &upload_cost), GNUNET_JSON_spec_end () @@ -930,7 +1018,6 @@ eval_provider_selection (struct PolicyBuilder *pb, GNUNET_break (0); pb->ec = TALER_EC_ANASTASIS_REDUCER_STATE_INVALID; pb->hint = "'methods' of provider"; - GNUNET_JSON_parse_free (pspec); for (unsigned int i = 0; i<pb->req_methods; i++) free_costs (policy_ent[i].usage_fee); return; @@ -952,14 +1039,12 @@ eval_provider_selection (struct PolicyBuilder *pb, { /* Provider does not OFFER this method, combination not possible. Cost is basically 'infinite', but we simply then skip this. */ - GNUNET_JSON_parse_free (pspec); GNUNET_JSON_parse_free (mspec); for (unsigned int i = 0; i<pb->req_methods; i++) free_costs (policy_ent[i].usage_fee); return; } GNUNET_JSON_parse_free (mspec); - GNUNET_JSON_parse_free (pspec); } /* calculate provider diversity by counting number of different @@ -1082,6 +1167,34 @@ provider_candidate (struct PolicyBuilder *pb, json_object_foreach (pb->providers, url, pconfig) { + const char *status; + uint32_t http_status = 0; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pconfig, + spec, + NULL, NULL)) + { + GNUNET_break (0); + continue; + } + if ( (MHD_HTTP_OK != http_status) || + (0 == strcmp (status, + "disabled")) ) + { + GNUNET_JSON_parse_free (spec); + continue; + } + GNUNET_JSON_parse_free (spec); prov_sel[i] = url; if (i == pb->req_methods - 1) { @@ -1171,58 +1284,6 @@ method_candidate (struct PolicyBuilder *pb, /** - * Lookup @a salt of @a provider_url in @a state. - * - * @param state the state to inspect - * @param provider_url provider to look into - * @param[out] salt value to extract - * @return #GNUNET_OK on success - */ -static int -lookup_salt (const json_t *state, - const char *provider_url, - struct ANASTASIS_CRYPTO_ProviderSaltP *salt) -{ - const json_t *aps; - const json_t *cfg; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("salt", - salt), - GNUNET_JSON_spec_end () - }; - - aps = json_object_get (state, - "authentication_providers"); - if (NULL == aps) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - cfg = json_object_get (aps, - provider_url); - if (NULL == cfg) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (MHD_HTTP_OK != - json_integer_value (json_object_get (cfg, - "http_status"))) - return GNUNET_NO; /* skip providers that are down */ - if (GNUNET_OK != - GNUNET_JSON_parse (cfg, - spec, - NULL, NULL)) - { - /* provider not working */ - GNUNET_break_op (0); - return GNUNET_NO; - } - return GNUNET_OK; -} - - -/** * Compare two cost lists. * * @param my cost to compare @@ -1604,7 +1665,8 @@ done_authentication (json_t *state, pb.methods = json_object_get (state, "authentication_methods"); if ( (NULL == pb.methods) || - (! json_is_array (pb.methods)) ) + (! json_is_array (pb.methods)) || + (json_array_size (pb.methods) > UINT_MAX) ) { ANASTASIS_redux_fail_ (cb, cb_cls, @@ -1612,7 +1674,8 @@ done_authentication (json_t *state, "'authentication_methods' must be provided"); return NULL; } - pb.num_methods = json_array_size (pb.methods); + pb.num_methods + = (unsigned int) json_array_size (pb.methods); switch (pb.num_methods) { case 0: @@ -1622,6 +1685,11 @@ done_authentication (json_t *state, "'authentication_methods' must not be empty"); return NULL; case 1: + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "Two factor authentication (2-FA) is required"); + return NULL; case 2: pb.req_methods = pb.num_methods; break; @@ -1685,9 +1753,9 @@ done_authentication (json_t *state, struct ANASTASIS_CRYPTO_ProviderSaltP salt; if (GNUNET_OK != - lookup_salt (state, - url, - &salt)) + ANASTASIS_reducer_lookup_salt (state, + url, + &salt)) continue; /* skip providers that are down */ provider = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("provider_url", @@ -1714,9 +1782,9 @@ done_authentication (json_t *state, url_str = json_string_value (url); if ( (NULL == url_str) || (GNUNET_OK != - lookup_salt (state, - url_str, - &salt)) ) + ANASTASIS_reducer_lookup_salt (state, + url_str, + &salt)) ) { GNUNET_break (0); ANASTASIS_redux_fail_ (cb, @@ -1877,8 +1945,8 @@ add_policy (json_t *state, { const char *provider_url; uint32_t method_idx; - json_t *prov_methods; const char *method_type; + const json_t *prov_methods; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("provider", &provider_url), @@ -1904,11 +1972,19 @@ add_policy (json_t *state, { const json_t *prov_cfg; uint32_t limit; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", &limit), - GNUNET_JSON_spec_json ("methods", - &prov_methods), + GNUNET_JSON_spec_array_const ("methods", + &prov_methods), GNUNET_JSON_spec_end () }; @@ -1924,10 +2000,6 @@ add_policy (json_t *state, "provider URL unknown"); return NULL; } - if (MHD_HTTP_OK != - json_integer_value (json_object_get (prov_cfg, - "http_status"))) - continue; if (GNUNET_OK != GNUNET_JSON_parse (prov_cfg, spec, @@ -1937,16 +2009,13 @@ add_policy (json_t *state, json_decref (methods); continue; } - if (! json_is_array (prov_methods)) + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) { - GNUNET_break (0); + /* skip provider, disabled or down */ json_decref (methods); - json_decref (prov_methods); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "provider lacks authentication methods"); - return NULL; + continue; } } @@ -1959,7 +2028,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, @@ -1972,7 +2040,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, @@ -2004,7 +2071,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, @@ -2021,7 +2087,6 @@ add_policy (json_t *state, { GNUNET_break (0); json_decref (methods); - json_decref (prov_methods); ANASTASIS_redux_fail_ (cb, cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, @@ -2032,7 +2097,6 @@ add_policy (json_t *state, GNUNET_assert (0 == json_array_append (methods, method)); - json_decref (prov_methods); } /* end of json_array_foreach (arg_array, index, method) */ } @@ -2323,12 +2387,12 @@ del_challenge (json_t *state, * @return number of years of service to pay for */ static unsigned int -expiration_to_years (struct GNUNET_TIME_Absolute expiration) +expiration_to_years (struct GNUNET_TIME_Timestamp expiration) { struct GNUNET_TIME_Relative rem; unsigned int years; - rem = GNUNET_TIME_absolute_get_remaining (expiration); + rem = GNUNET_TIME_absolute_get_remaining (expiration.abs_time); years = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us; if (0 != rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us) years++; @@ -2348,7 +2412,7 @@ expiration_to_years (struct GNUNET_TIME_Absolute expiration) */ static enum GNUNET_GenericReturnValue update_expiration_cost (json_t *state, - struct GNUNET_TIME_Absolute expiration) + struct GNUNET_TIME_Timestamp expiration) { struct Costs *costs = NULL; unsigned int years; @@ -2373,26 +2437,33 @@ update_expiration_cost (json_t *state, json_object_foreach (providers, url, provider) { struct TALER_Amount annual_fee; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), TALER_JSON_spec_amount_any ("annual_fee", &annual_fee), GNUNET_JSON_spec_end () }; struct TALER_Amount fee; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (provider, - "http_status"))) - continue; /* skip providers that are down */ if (GNUNET_OK != GNUNET_JSON_parse (provider, pspec, NULL, NULL)) { - /* strange, skip as well */ - GNUNET_break_op (0); + /* likely down, skip */ continue; } + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) + continue; /* skip providers that are down or disabled */ if (0 > TALER_amount_multiply (&fee, &annual_fee, @@ -2473,7 +2544,15 @@ update_expiration_cost (json_t *state, off++; { struct TALER_Amount upload_cost; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), TALER_JSON_spec_amount_any ("truth_upload_fee", &upload_cost), GNUNET_JSON_spec_end () @@ -2491,6 +2570,13 @@ update_expiration_cost (json_t *state, GNUNET_break (0); return GNUNET_SYSERR; } + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } if (0 > TALER_amount_multiply (&fee, &upload_cost, @@ -2519,8 +2605,7 @@ update_expiration_cost (json_t *state, { struct Costs *nxt = costs->next; - if ( (0 != costs->cost.value) || - (0 != costs->cost.fraction) ) + if (! TALER_amount_is_zero (&costs->cost)) { json_t *ao; @@ -2542,13 +2627,12 @@ update_expiration_cost (json_t *state, } if (is_free) - expiration = GNUNET_TIME_relative_to_absolute (ANASTASIS_FREE_STORAGE); + expiration = GNUNET_TIME_relative_to_timestamp (ANASTASIS_FREE_STORAGE); /* update 'expiration' in state */ { json_t *eo; - (void) GNUNET_TIME_round_abs (&expiration); - eo = GNUNET_JSON_from_time_abs (expiration); + eo = GNUNET_JSON_from_timestamp (expiration); GNUNET_assert (0 == json_object_set_new (state, "expiration", @@ -2589,11 +2673,13 @@ done_policy_review (json_t *state, return NULL; } { - struct GNUNET_TIME_Absolute exp = {0}; + struct GNUNET_TIME_Timestamp exp + = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_absolute_time ("expiration", - &exp)), + GNUNET_JSON_spec_timestamp ("expiration", + &exp), + NULL), GNUNET_JSON_spec_end () }; @@ -2608,8 +2694,8 @@ done_policy_review (json_t *state, "invalid expiration specified"); return NULL; } - if (0 == exp.abs_value_us) - exp = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS); + if (GNUNET_TIME_absolute_is_zero (exp.abs_time)) + exp = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS); if (GNUNET_OK != update_expiration_cost (state, exp)) @@ -2869,6 +2955,62 @@ serialize_truth (struct UploadContext *uc) /** + * Test if the given @a provider_url is used by any of the + * authentication methods and thus the provider should be + * considered mandatory for storing the policy. + * + * @param state state to inspect + * @param provider_url provider to test + * @return false if the provider can be removed from policy + * upload considerations without causing a problem + */ +static bool +provider_required (const json_t *state, + const char *provider_url) +{ + json_t *policies + = json_object_get (state, + "policies"); + size_t pidx; + json_t *policy; + + json_array_foreach (policies, pidx, policy) + { + json_t *methods = json_object_get (policy, + "methods"); + size_t midx; + json_t *method; + + json_array_foreach (methods, midx, method) + { + const char *provider + = json_string_value (json_object_get (method, + "provider")); + + if (NULL == provider) + { + GNUNET_break (0); + continue; + } + if (0 == strcmp (provider, + provider_url)) + return true; + } + } + return false; +} + + +/** + * All truth uploads are done, begin with uploading the policy. + * + * @param[in,out] uc context for the operation + */ +static void +share_secret (struct UploadContext *uc); + + +/** * Function called with the results of a #ANASTASIS_secret_share(). * * @param cls closure with a `struct UploadContext *` @@ -2901,8 +3043,8 @@ secret_share_result_cb (void *cls, d = GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("policy_version", pssi->policy_version), - GNUNET_JSON_pack_time_abs ("policy_expiration", - pssi->policy_expiration)); + GNUNET_JSON_pack_timestamp ("policy_expiration", + pssi->policy_expiration)); GNUNET_assert (NULL != d); GNUNET_assert (0 == json_object_set_new (sa, @@ -2952,7 +3094,8 @@ secret_share_result_cb (void *cls, json_array_foreach (providers, off, provider) { const char *purl = json_string_value (json_object_get (provider, - "provider_url")); + "provider_url") + ); if (NULL == purl) { @@ -2993,13 +3136,53 @@ secret_share_result_cb (void *cls, { json_t *details; + if (! provider_required (uc->state, + sr->details.provider_failure.provider_url)) + { + /* try again without that provider */ + json_t *provider; + json_t *providers; + size_t idx; + + provider + = json_object_get ( + json_object_get (uc->state, + "authentication_providers"), + sr->details.provider_failure.provider_url); + GNUNET_break (0 == + json_object_set_new (provider, + "status", + json_string ("disabled"))); + providers + = json_object_get (uc->state, + "policy_providers"); + json_array_foreach (providers, idx, provider) + { + const char *url + = json_string_value (json_object_get (provider, + "provider_url")); + + if ( (NULL != url) && + (0 == strcmp (sr->details.provider_failure.provider_url, + url)) ) + { + GNUNET_break (0 == + json_array_remove (providers, + idx)); + break; + } + } + share_secret (uc); + return; + } details = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("backup_state", - "ERROR"), GNUNET_JSON_pack_uint64 ("http_status", sr->details.provider_failure.http_status), - GNUNET_JSON_pack_uint64 ("upload_status", + GNUNET_JSON_pack_uint64 ("code", sr->details.provider_failure.ec), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint ( + sr->details.provider_failure.ec)), GNUNET_JSON_pack_string ("provider_url", sr->details.provider_failure.provider_url)); uc->cb (uc->cb_cls, @@ -3028,26 +3211,29 @@ secret_share_result_cb (void *cls, static void share_secret (struct UploadContext *uc) { - json_t *user_id; - json_t *core_secret; - json_t *jpolicies; - json_t *providers = NULL; + const json_t *user_id; + const json_t *core_secret; + const json_t *jpolicies; + const json_t *providers = NULL; size_t policies_len; const char *secret_name = NULL; unsigned int pds_len; struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("identity_attributes", - &user_id), - GNUNET_JSON_spec_json ("policies", - &jpolicies), - GNUNET_JSON_spec_json ("policy_providers", - &providers), - GNUNET_JSON_spec_json ("core_secret", - &core_secret), + GNUNET_JSON_spec_object_const ("identity_attributes", + &user_id), + GNUNET_JSON_spec_array_const ("policies", + &jpolicies), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("policy_providers", + &providers), + NULL), + GNUNET_JSON_spec_object_const ("core_secret", + &core_secret), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("secret_name", - &secret_name)), + &secret_name), + NULL), GNUNET_JSON_spec_end () }; @@ -3069,12 +3255,13 @@ share_secret (struct UploadContext *uc) struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_end () }; args = json_object_get (uc->state, - "pay-arguments"); + "pay_arguments"); if ( (NULL != args) && (GNUNET_OK != GNUNET_JSON_parse (args, @@ -3094,40 +3281,43 @@ share_secret (struct UploadContext *uc) } } - if ( (! json_is_object (user_id)) || - (! json_is_array (jpolicies)) || - (0 == json_array_size (jpolicies)) || - ( (NULL != providers) && - (! json_is_array (providers)) ) ) + policies_len = json_array_size (jpolicies); + if (0 == policies_len) { ANASTASIS_redux_fail_ (uc->cb, uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "State parsing failed checks when preparing to share secret"); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } - policies_len = json_array_size (jpolicies); - pds_len = json_array_size (providers); - + if (json_array_size (providers) > UINT_MAX) + { + GNUNET_break_op (0); + ANASTASIS_redux_fail_ (uc->cb, + uc->cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "provider array excessively long"); + upload_cancel_cb (uc); + return; + } + pds_len + = (unsigned int) json_array_size (providers); if (0 == pds_len) { ANASTASIS_redux_fail_ (uc->cb, uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "no workable providers in state"); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } - { struct ANASTASIS_Policy *vpolicies[policies_len]; const struct ANASTASIS_Policy *policies[policies_len]; - struct ANASTASIS_ProviderDetails pds[GNUNET_NZL (pds_len)]; + struct ANASTASIS_ProviderDetails pds[pds_len]; /* initialize policies/vpolicies arrays */ memset (pds, @@ -3142,18 +3332,19 @@ share_secret (struct UploadContext *uc) unsigned int methods_len; if ( (! json_is_array (jmethods)) || - (0 == json_array_size (jmethods)) ) + (0 == json_array_size (jmethods)) || + (json_array_size (jmethods) > UINT_MAX) ) { GNUNET_break (0); ANASTASIS_redux_fail_ (uc->cb, uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "'methods' must be an array"); - GNUNET_JSON_parse_free (spec); + "'methods' must be an array of sane length"); upload_cancel_cb (uc); return; } - methods_len = json_array_size (jmethods); + methods_len + = (unsigned int) json_array_size (jmethods); { struct ANASTASIS_Policy *p; struct ANASTASIS_Truth *truths[methods_len]; @@ -3163,13 +3354,14 @@ share_secret (struct UploadContext *uc) { const json_t *jmethod = json_array_get (jmethods, j); - json_t *jtruth = NULL; + const json_t *jtruth = NULL; uint32_t truth_index; const char *provider_url; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("truth", - &jtruth)), + GNUNET_JSON_spec_object_const ("truth", + &jtruth), + NULL), GNUNET_JSON_spec_string ("provider", &provider_url), GNUNET_JSON_spec_uint32 ("authentication_method", @@ -3190,7 +3382,6 @@ share_secret (struct UploadContext *uc) uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'truth' failed to decode"); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } @@ -3207,8 +3398,6 @@ share_secret (struct UploadContext *uc) uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'truth' failed to decode"); - GNUNET_JSON_parse_free (ispec); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } @@ -3246,13 +3435,10 @@ share_secret (struct UploadContext *uc) uc->cb_cls, TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, "'truth' failed to decode"); - GNUNET_JSON_parse_free (ispec); - GNUNET_JSON_parse_free (spec); upload_cancel_cb (uc); return; } } - GNUNET_JSON_parse_free (ispec); ctruths[j] = truths[j]; } p = ANASTASIS_policy_create (ctruths, @@ -3272,7 +3458,8 @@ share_secret (struct UploadContext *uc) struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("payment_secret", - &pds[i].payment_secret)), + &pds[i].payment_secret), + NULL), GNUNET_JSON_spec_string ("provider_url", &pds[i].provider_url), GNUNET_JSON_spec_end () @@ -3283,9 +3470,9 @@ share_secret (struct UploadContext *uc) ispec, NULL, NULL)) || (GNUNET_OK != - lookup_salt (uc->state, - pds[i].provider_url, - &pds[i].provider_salt)) ) + ANASTASIS_reducer_lookup_salt (uc->state, + pds[i].provider_url, + &pds[i].provider_salt)) ) { GNUNET_break (0); ANASTASIS_redux_fail_ (uc->cb, @@ -3295,7 +3482,6 @@ share_secret (struct UploadContext *uc) for (unsigned int i = 0; i<policies_len; i++) ANASTASIS_policy_destroy (vpolicies[i]); upload_cancel_cb (uc); - GNUNET_JSON_parse_free (spec); return; } } @@ -3326,7 +3512,6 @@ share_secret (struct UploadContext *uc) for (unsigned int i = 0; i<policies_len; i++) ANASTASIS_policy_destroy (vpolicies[i]); } - GNUNET_JSON_parse_free (spec); if (NULL == uc->ss) { GNUNET_break (0); @@ -3519,7 +3704,8 @@ add_truth_object (struct UploadContext *uc, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("upload_status", - &status)), + &status), + NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != @@ -3568,9 +3754,9 @@ add_truth_object (struct UploadContext *uc, }; if (GNUNET_OK != - lookup_salt (uc->state, - provider_url, - &salt)) + ANASTASIS_reducer_lookup_salt (uc->state, + provider_url, + &salt)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -3703,10 +3889,12 @@ check_truth_upload (struct UploadContext *uc, &type), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("mime_type", - &mime_type)), + &mime_type), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("instructions", - &instructions)), + &instructions), + NULL), GNUNET_JSON_spec_varsize ("challenge", &truth_data, &truth_data_size), @@ -3737,9 +3925,9 @@ check_truth_upload (struct UploadContext *uc, tue->am_idx = am_idx; tue->policies_length = 1; if (GNUNET_OK != - lookup_salt (uc->state, - provider_url, - &provider_salt)) + ANASTASIS_reducer_lookup_salt (uc->state, + provider_url, + &provider_salt)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); @@ -3755,7 +3943,6 @@ check_truth_upload (struct UploadContext *uc, struct ANASTASIS_CRYPTO_TruthKeyP truth_key; struct ANASTASIS_CRYPTO_KeyShareP key_share; struct ANASTASIS_CRYPTO_NonceP nonce; - struct GNUNET_JSON_Specification jspec[] = { GNUNET_JSON_spec_fixed_auto ("salt", &question_salt), @@ -3841,10 +4028,10 @@ upload (json_t *state, struct UploadContext *uc; json_t *auth_methods; json_t *policies; - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_absolute_time ("expiration", - &expiration), + GNUNET_JSON_spec_timestamp ("expiration", + &expiration), GNUNET_JSON_spec_end () }; @@ -3895,12 +4082,13 @@ upload (json_t *state, struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &uc->timeout)), + &uc->timeout), + NULL), GNUNET_JSON_spec_end () }; args = json_object_get (uc->state, - "pay-arguments"); + "pay_arguments"); if ( (NULL != args) && (GNUNET_OK != GNUNET_JSON_parse (args, @@ -3954,7 +4142,8 @@ upload (json_t *state, &am_idx), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("truth", - &truth)), + &truth), + NULL), GNUNET_JSON_spec_end () }; @@ -4111,17 +4300,23 @@ check_upload_size_limit (json_t *state, see #6760. */ json_object_foreach (aps, url, ap) { - uint32_t limit; + uint32_t limit = 0; + const char *status; + uint32_t http_status = 0; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", - &limit), + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", + &limit), + NULL), GNUNET_JSON_spec_end () }; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (ap, - "http_status"))) - continue; /* skip providers that are down */ if (GNUNET_OK != GNUNET_JSON_parse (ap, spec, @@ -4131,6 +4326,10 @@ check_upload_size_limit (json_t *state, GNUNET_break_op (0); continue; } + if ( (MHD_HTTP_OK != http_status) || + (0 != strcmp (status, + "ok")) ) + continue; if (0 == limit) return GNUNET_SYSERR; min_limit = GNUNET_MIN (min_limit, @@ -4161,13 +4360,15 @@ enter_secret (json_t *state, void *cb_cls) { json_t *jsecret; - struct GNUNET_TIME_Absolute expiration = {0}; + struct GNUNET_TIME_Timestamp expiration + = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("secret", &jsecret), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_absolute_time ("expiration", - &expiration)), + GNUNET_JSON_spec_timestamp ("expiration", + &expiration), + NULL), GNUNET_JSON_spec_end () }; @@ -4215,7 +4416,7 @@ enter_secret (json_t *state, break; } } - if (0 != expiration.abs_value_us) + if (! GNUNET_TIME_absolute_is_zero (expiration.abs_time)) { if (GNUNET_OK != update_expiration_cost (state, @@ -4346,10 +4547,10 @@ update_expiration (json_t *state, ANASTASIS_ActionCallback cb, void *cb_cls) { - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_absolute_time ("expiration", - &expiration), + GNUNET_JSON_spec_timestamp ("expiration", + &expiration), GNUNET_JSON_spec_end () }; @@ -4483,7 +4684,7 @@ pay_truths_backup (json_t *state, if (NULL != arguments) GNUNET_assert (0 == json_object_set (state, - "pay-arguments", + "pay_arguments", (json_t *) arguments)); return upload (state, cb, @@ -4513,7 +4714,7 @@ pay_policies_backup (json_t *state, if (NULL != arguments) GNUNET_assert (0 == json_object_set (state, - "pay-arguments", + "pay_arguments", (json_t *) arguments)); return upload (state, cb, @@ -4596,6 +4797,11 @@ ANASTASIS_backup_action_ (json_t *state, }, { ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING, + "poll_providers", + &ANASTASIS_REDUX_poll_providers_ + }, + { + ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING, "back", &ANASTASIS_back_generic_decrement_ }, @@ -4674,7 +4880,7 @@ ANASTASIS_backup_action_ (json_t *state, "back", &back_finished }, - { ANASTASIS_BACKUP_STATE_ERROR, NULL, NULL } + { ANASTASIS_BACKUP_STATE_INVALID, NULL, NULL } }; const char *s = json_string_value (json_object_get (state, "backup_state")); @@ -4682,7 +4888,7 @@ ANASTASIS_backup_action_ (json_t *state, GNUNET_assert (NULL != s); /* holds as per invariant of caller */ bs = ANASTASIS_backup_state_from_string_ (s); - if (ANASTASIS_BACKUP_STATE_ERROR == bs) + if (ANASTASIS_BACKUP_STATE_INVALID == bs) { ANASTASIS_redux_fail_ (cb, cb_cls, @@ -4935,7 +5141,25 @@ ANASTASIS_REDUX_backup_begin_ (json_t *state, json_object_foreach (provider_list, url, prov) { struct BackupStartStateProviderEntry *pe; json_t *istate; + const char *status; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (prov, + spec, + NULL, NULL)) + { + /* skip malformed provider entry */ + GNUNET_break_op (0); + continue; + } + if (0 == strcmp (status, + "disabled")) + continue; pe = GNUNET_new (struct BackupStartStateProviderEntry); pe->bss = bss; istate = json_object (); diff --git a/src/reducer/anastasis_api_discovery.c b/src/reducer/anastasis_api_discovery.c new file mode 100644 index 0000000..3470d97 --- /dev/null +++ b/src/reducer/anastasis_api_discovery.c @@ -0,0 +1,549 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/anastasis_api_discovery.c + * @brief anastasis recovery policy discovery api + * @author Christian Grothoff + */ +#include <platform.h> +#include <jansson.h> +#include "anastasis_redux.h" +#include "anastasis_error_codes.h" +#include <taler/taler_json_lib.h> +#include "anastasis_api_redux.h" +#include <dlfcn.h> + + +/** + * Handle for one request we are doing at a specific provider. + */ +struct ProviderOperation +{ + /** + * Kept in a DLL. + */ + struct ProviderOperation *next; + + /** + * Kept in a DLL. + */ + struct ProviderOperation *prev; + + /** + * Base URL of the provider. + */ + char *provider_url; + + /** + * Handle to the version check operation we are performing. + */ + struct ANASTASIS_VersionCheck *vc; + + /** + * Handle discovery operation we this is a part of. + */ + struct ANASTASIS_PolicyDiscovery *pd; + + /** + * Attribute mask applied to the identity attributes + * for this operation. + */ + json_int_t attribute_mask; +}; + + +/** + * Handle for a discovery operation. + */ +struct ANASTASIS_PolicyDiscovery +{ + /** + * Head of HTTP requests, kept in a DLL. + */ + struct ProviderOperation *po_head; + + /** + * Tail of HTTP requests, kept in a DLL. + */ + struct ProviderOperation *po_tail; + + /** + * Function to call with results. + */ + ANASTASIS_PolicyDiscoveryCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Map for duplicate detection, maps hashes of policies we + * have already seen to a json_array with all providers + * and versions corresponding to this policy hash. + */ + struct GNUNET_CONTAINER_MultiHashMap *dd_map; + + /** + * State we are operating on. + */ + json_t *state; + + /** + * Number of optional fields in our identity attributes. + */ + json_int_t opt_cnt; +}; + + +/** + * Callback which passes back meta data about one of the + * recovery documents available at the provider. + * + * @param cls our `struct ProviderOperation *` + * @param version version number of the policy document, + * 0 for the end of the list + * @param server_time time of the backup at the provider + * @param recdoc_id hash of the compressed recovery document, uniquely + * identifies the document; NULL for the end of the list + * @param secret_name name of the secret as chosen by the user, + * or NULL if the user did not provide a name + */ +static void +meta_cb (void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp server_time, + const struct GNUNET_HashCode *recdoc_id, + const char *secret_name) +{ + struct ProviderOperation *po = cls; + struct ANASTASIS_PolicyDiscovery *pd = po->pd; + json_t *pa; + json_t *pe; + + if (NULL == recdoc_id) + { + po->vc = NULL; + GNUNET_CONTAINER_DLL_remove (pd->po_head, + pd->po_tail, + po); + GNUNET_free (po->provider_url); + GNUNET_free (po); + return; + } + pe = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("version", + version), + GNUNET_JSON_pack_string ("url", + po->provider_url)); + + pa = GNUNET_CONTAINER_multihashmap_get (pd->dd_map, + recdoc_id); + if (NULL != pa) + { + GNUNET_break (0 == + json_array_append_new (pa, + pe)); + return; + } + pa = json_array (); + GNUNET_assert (NULL != pa); + GNUNET_break (0 == + json_array_append_new (pa, + pe)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + pd->dd_map, + recdoc_id, + pa, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + pd->cb (pd->cb_cls, + recdoc_id, + po->provider_url, + version, + po->attribute_mask, + server_time, + secret_name, + pa); +} + + +/** + * Start policy operation for @a pd using identity @a id_data + * at provider @a provider_url. + * + * @param pd policy discovery operation + * @param id_data our identity data, derived using @a mask + * @param mask the mask describing which optional attributes were removed + * @param provider_url which provider to query + * @param cursor cursor telling us from where to query + */ +static void +start_po (struct ANASTASIS_PolicyDiscovery *pd, + const json_t *id_data, + json_int_t mask, + const char *provider_url, + const json_t *cursor) +{ + const json_t *state = pd->state; + struct ProviderOperation *po; + uint32_t max_version = UINT32_MAX; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + + if (NULL != cursor) + { + size_t i; + json_t *obj; + + json_array_foreach ((json_t *) cursor, i, obj) + { + const char *url; + uint64_t cmask; + uint32_t mv; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("provider_url", + &url), + GNUNET_JSON_spec_uint64 ("mask", + &cmask), + GNUNET_JSON_spec_uint32 ("max_version", + &mv), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, NULL)) + { + /* cursor invalid */ + GNUNET_break (0); + json_dumpf (obj, + stderr, + JSON_INDENT (2)); + return; + } + if ( (cmask == mask) && + (0 == strcmp (url, + provider_url)) ) + { + max_version = mv; + break; + } + } + } + + if (GNUNET_OK != + ANASTASIS_reducer_lookup_salt (state, + provider_url, + &provider_salt)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No /config for `%s', skipping provider\n", + provider_url); + return; + } + po = GNUNET_new (struct ProviderOperation); + po->pd = pd; + po->attribute_mask = mask; + po->provider_url = GNUNET_strdup (provider_url); + po->vc = ANASTASIS_recovery_get_versions (ANASTASIS_REDUX_ctx_, + id_data, + max_version, + provider_url, + &provider_salt, + &meta_cb, + po); + if (NULL == po->vc) + { + GNUNET_free (po); + } + else + { + GNUNET_CONTAINER_DLL_insert (pd->po_head, + pd->po_tail, + po); + } +} + + +struct ANASTASIS_PolicyDiscovery * +ANASTASIS_policy_discovery_start (const json_t *state, + const json_t *cursor, + ANASTASIS_PolicyDiscoveryCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyDiscovery *pd; + json_t *master_id = json_object_get (state, + "identity_attributes"); + json_t *providers = json_object_get (state, + "authentication_providers"); + json_t *required_attributes = json_object_get (state, + "required_attributes"); + unsigned int opt_cnt; + + if ( (NULL == master_id) || + (! json_is_object (master_id)) ) + { + GNUNET_break (0); + json_dumpf (state, + stderr, + JSON_INDENT (2)); + return NULL; + } + if ( (NULL == providers) || + (! json_is_object (providers)) ) + { + GNUNET_break (0); + json_dumpf (state, + stderr, + JSON_INDENT (2)); + return NULL; + } + if ( (NULL == required_attributes) || + (! json_is_array (required_attributes)) ) + { + GNUNET_break (0); + json_dumpf (required_attributes, + stderr, + JSON_INDENT (2)); + return NULL; + } + + /* count optional attributes present in 'master_id' */ + opt_cnt = 0; + { + size_t index; + json_t *required_attribute; + + json_array_foreach (required_attributes, + index, + required_attribute) + { + const char *name; + int optional = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_boolean ("optional", + &optional), + NULL), + GNUNET_JSON_spec_end () + }; + bool present; + + if (GNUNET_OK != + GNUNET_JSON_parse (required_attribute, + spec, + NULL, NULL)) + { + GNUNET_break (0); + json_dumpf (required_attribute, + stderr, + JSON_INDENT (2)); + return NULL; + } + present = (NULL != + json_object_get (master_id, + name)); + if ((! present) && (! optional)) + { + GNUNET_break (0); + json_dumpf (master_id, + stderr, + JSON_INDENT (2)); + return NULL; + } + if (present && optional) + opt_cnt++; + } + } + + pd = GNUNET_new (struct ANASTASIS_PolicyDiscovery); + pd->dd_map = GNUNET_CONTAINER_multihashmap_create (128, + GNUNET_NO); + pd->cb = cb; + pd->cb_cls = cb_cls; + pd->opt_cnt = opt_cnt; + pd->state = json_deep_copy (state); + + /* Compute 'id_data' for all possible masks, and then + start downloads at all providers for 'id_data' */ + for (json_int_t mask = 0; mask < (1LL << opt_cnt); mask++) + { + json_t *id_data = ANASTASIS_mask_id_data (state, + master_id, + mask); + json_t *value; + const char *url; + + json_object_foreach (providers, url, value) + { + start_po (pd, + id_data, + mask, + url, + cursor); + } + json_decref (id_data); + } + return pd; +} + + +void +ANASTASIS_policy_discovery_more (struct ANASTASIS_PolicyDiscovery *pd, + const char *provider_url, + json_t *provider_state) +{ + json_t *master_id = json_object_get (pd->state, + "identity_attributes"); + json_t *providers = json_object_get (pd->state, + "authentication_providers"); + + GNUNET_assert (NULL != master_id); + GNUNET_assert (NULL != providers); + GNUNET_assert (0 == + json_object_set (providers, + provider_url, + provider_state)); + /* Compute 'id_data' for all possible masks, and then + start downloads at provider for 'id_data' */ + for (json_int_t mask = 0; mask < (1LL << pd->opt_cnt); mask++) + { + json_t *id_data = ANASTASIS_mask_id_data (pd->state, + master_id, + mask); + + start_po (pd, + id_data, + mask, + provider_url, + NULL); + json_decref (id_data); + } +} + + +/** + * Free JSON Arrays from our hash map. + * + * @param cls NULL + * @param key ignored + * @param value `json_t *` to free + * @return #GNUNET_OK + */ +static enum GNUNET_GenericReturnValue +free_dd_json (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + json_t *j = value; + + (void) cls; + (void) key; + json_decref (j); + return GNUNET_OK; +} + + +void +ANASTASIS_policy_discovery_stop (struct ANASTASIS_PolicyDiscovery *pd) +{ + struct ProviderOperation *po; + + while (NULL != (po = pd->po_head)) + { + GNUNET_CONTAINER_DLL_remove (pd->po_head, + pd->po_tail, + po); + ANASTASIS_recovery_get_versions_cancel (po->vc); + GNUNET_free (po->provider_url); + GNUNET_free (po); + } + GNUNET_CONTAINER_multihashmap_iterate (pd->dd_map, + &free_dd_json, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (pd->dd_map); + json_decref (pd->state); + GNUNET_free (pd); +} + + +json_t * +ANASTASIS_mask_id_data (const json_t *state, + const json_t *master_id, + json_int_t mask) +{ + json_t *required_attributes = json_object_get (state, + "required_attributes"); + size_t index; + json_t *required_attribute; + json_t *ret = json_deep_copy (master_id); + unsigned int bit = 0; + + if ( (NULL == required_attributes) || + (! json_is_array (required_attributes)) ) + { + GNUNET_break (0); + return NULL; + } + + json_array_foreach (required_attributes, index, required_attribute) + { + const char *name; + int optional = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_boolean ("optional", + &optional), + NULL), + GNUNET_JSON_spec_end () + }; + bool present; + + if (GNUNET_OK != + GNUNET_JSON_parse (required_attribute, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return NULL; + } + present = (NULL != + json_object_get (master_id, + name)); + if ((! present) && (! optional)) + { + GNUNET_break (0); + return NULL; + } + if (present && optional) + { + if (0 != ((1LL << bit) & mask)) + { + GNUNET_assert (0 == + json_object_del (ret, + name)); + } + bit++; + } + } + return ret; +} diff --git a/src/reducer/anastasis_api_providers.c b/src/reducer/anastasis_api_providers.c new file mode 100644 index 0000000..82243f5 --- /dev/null +++ b/src/reducer/anastasis_api_providers.c @@ -0,0 +1,300 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021, 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/anastasis_api_providers.c + * @brief anastasis provider synchronization logic + * @author Christian Grothoff + */ + +#include <platform.h> +#include <jansson.h> +#include "anastasis_redux.h" +#include "anastasis_error_codes.h" +#include "anastasis_api_redux.h" + + +/** + * Main data structure for sync_providers(). + */ +struct MasterSync; + + +/** + * Data structure for one provider we are syncing /config with. + */ +struct SyncEntry +{ + /** + * Kept in a DLL. + */ + struct SyncEntry *next; + + /** + * Kept in a DLL. + */ + struct SyncEntry *prev; + + /** + * Sync operation we are part of. + */ + struct MasterSync *ms; + + /** + * Redux action for this provider. + */ + struct ANASTASIS_ReduxAction *ra; +}; + + +/** + * Main data structure for sync_providers(). + */ +struct MasterSync +{ + /** + * Our own sync action we expose externally. + */ + struct ANASTASIS_ReduxAction ra; + /** + * Head of DLL with entries per provider. + */ + struct SyncEntry *se_head; + /** + * Tail of DLL with entries per provider. + */ + struct SyncEntry *se_tail; + + /** + * Function to call with the result. + */ + ANASTASIS_ActionCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + +}; + + +/** + * Free @a cls data structure. + * + * @param[in] cls data structure to free, must be a `struct MasterSync *` + */ +static void +clean_sync (void *cls) +{ + struct MasterSync *ms = cls; + struct SyncEntry *se; + + while (NULL != (se = ms->se_head)) + { + GNUNET_CONTAINER_DLL_remove (ms->se_head, + ms->se_tail, + se); + se->ra->cleanup (se->ra->cleanup_cls); + GNUNET_free (se); + } + GNUNET_free (ms); +} + + +/** + * Function called when we have made progress on any of the + * providers we are trying to sync with. + * + * @param cls closure + * @param error error code, #TALER_EC_NONE if @a new_bs is the new successful state + * @param new_state the new state of the operation (client should json_incref() to keep an alias) + */ +static void +sync_progress (void *cls, + enum TALER_ErrorCode error, + json_t *new_state) +{ + struct SyncEntry *se = cls; + struct MasterSync *ms = se->ms; + + GNUNET_CONTAINER_DLL_remove (ms->se_head, + ms->se_tail, + se); + GNUNET_free (se); + ms->cb (ms->cb_cls, + error, + new_state); + clean_sync (ms); +} + + +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_sync_providers_ (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) +{ + json_t *rd; + json_t *cs_arr; + struct MasterSync *ms; + + rd = json_object_get (state, + "recovery_document"); + if (NULL == rd) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'recovery_document' missing"); + return NULL; + } + cs_arr = json_object_get (rd, + "challenges"); + if (! json_is_array (cs_arr)) + { + GNUNET_break_op (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'recovery_document' must be an array"); + return NULL; + } + ms = GNUNET_new (struct MasterSync); + ms->cb = cb; + ms->cb_cls = cb_cls; + { + json_t *cs; + unsigned int n_index; + + json_array_foreach (cs_arr, n_index, cs) + { + const char *provider_url; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("url", + &provider_url), + GNUNET_JSON_spec_end () + }; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + struct SyncEntry *se; + + if (GNUNET_OK != + GNUNET_JSON_parse (cs, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'recovery_document' missing"); + clean_sync (ms); + return NULL; + } + if (GNUNET_OK == + ANASTASIS_reducer_lookup_salt (state, + provider_url, + &provider_salt)) + continue; /* provider already ready */ + se = GNUNET_new (struct SyncEntry); + se->ms = ms; + GNUNET_CONTAINER_DLL_insert (ms->se_head, + ms->se_tail, + se); + se->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url, + state, + &sync_progress, + se); + } + } + if (NULL == ms->se_head) + { + /* everything already synced */ + clean_sync (ms); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED, + "already in sync"); + return NULL; + } + ms->ra.cleanup = &clean_sync; + ms->ra.cleanup_cls = ms; + return &ms->ra; +} + + +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_poll_providers_ (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) +{ + json_t *ap; + const char *url; + json_t *obj; + struct MasterSync *ms; + + ap = json_object_get (state, + "authentication_providers"); + if (NULL == ap) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'authentication_providers' missing"); + return NULL; + } + ms = GNUNET_new (struct MasterSync); + ms->cb = cb; + ms->cb_cls = cb_cls; + json_object_foreach (ap, url, obj) + { + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + struct SyncEntry *se; + struct ANASTASIS_ReduxAction *ra; + + if (GNUNET_OK == + ANASTASIS_reducer_lookup_salt (state, + url, + &provider_salt)) + continue; + se = GNUNET_new (struct SyncEntry); + se->ms = ms; + GNUNET_CONTAINER_DLL_insert (ms->se_head, + ms->se_tail, + se); + ra = ANASTASIS_REDUX_add_provider_to_state_ (url, + state, + &sync_progress, + se); + if (NULL == ra) + return NULL; /* sync_progress already called! */ + se->ra = ra; + } + if (NULL == ms->se_head) + { + /* everything already synced */ + clean_sync (ms); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, + "already in sync"); + return NULL; + } + ms->ra.cleanup = &clean_sync; + ms->ra.cleanup_cls = ms; + return &ms->ra; +} diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c index 897a6dd..e795c55 100644 --- a/src/reducer/anastasis_api_recovery_redux.c +++ b/src/reducer/anastasis_api_recovery_redux.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -44,7 +44,7 @@ ANASTASIS_recovery_state_from_string_ (const char *state_string) if (0 == strcmp (state_string, recovery_strings[i])) return i; - return ANASTASIS_RECOVERY_STATE_ERROR; + return ANASTASIS_RECOVERY_STATE_INVALID; } @@ -83,11 +83,79 @@ json_t * ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg) { json_t *initial_state; + const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer (); + + if (NULL != external_reducer) + { + int pipefd_stdout[2]; + pid_t pid = 0; + int status; + FILE *reducer_stdout; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Using external reducer '%s' for recovery start status\n", + external_reducer); + + GNUNET_assert (0 == pipe (pipefd_stdout)); + pid = fork (); + if (pid == 0) + { + (void) close (pipefd_stdout[0]); + (void) dup2 (pipefd_stdout[1], + STDOUT_FILENO); + execlp (external_reducer, + external_reducer, + "-r", + NULL); + GNUNET_assert (0); + } + + close (pipefd_stdout[1]); + reducer_stdout = fdopen (pipefd_stdout[0], + "r"); + { + json_error_t err; + + initial_state = json_loadf (reducer_stdout, + 0, + &err); + + if (NULL == initial_state) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "External reducer did not output valid JSON: %s:%d:%d %s\n", + err.source, + err.line, + err.column, + err.text); + GNUNET_assert (0 == fclose (reducer_stdout)); + waitpid (pid, &status, 0); + return NULL; + } + } + + GNUNET_assert (NULL != initial_state); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Waiting for external reducer to terminate.\n"); + GNUNET_assert (0 == fclose (reducer_stdout)); + reducer_stdout = NULL; + waitpid (pid, &status, 0); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "External reducer finished with exit status '%d'\n", + status); + return initial_state; + } (void) cfg; initial_state = ANASTASIS_REDUX_load_continents_ (); if (NULL == initial_state) return NULL; + GNUNET_assert ( + 0 == + json_object_set_new (initial_state, + "reducer_type", + json_string ("recovery"))); set_state (initial_state, ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING); return initial_state; @@ -188,15 +256,16 @@ sctx_free (void *cls) /** - * Update @a state to reflect the error provided in @a rc. + * Call the action callback with an error result * - * @param[in,out] state state to update + * @param cb action callback to call + * @param cb_cls closure for @a cb * @param rc error code to translate to JSON - * @return error code to use */ -static enum TALER_ErrorCode -update_state_by_error (json_t *state, - enum ANASTASIS_RecoveryStatus rc) +void +fail_by_error (ANASTASIS_ActionCallback cb, + void *cb_cls, + enum ANASTASIS_RecoveryStatus rc) { const char *msg = NULL; enum TALER_ErrorCode ec = TALER_EC_INVALID; @@ -249,17 +318,10 @@ update_state_by_error (json_t *state, ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED; break; } - GNUNET_assert (0 == - json_object_set_new (state, - "error_message", - json_string (msg))); - GNUNET_assert (0 == - json_object_set_new (state, - "error_code", - json_integer (rc))); - set_state (state, - ANASTASIS_RECOVERY_STATE_ERROR); - return ec; + ANASTASIS_redux_fail_ (cb, + cb_cls, + ec, + msg); } @@ -279,7 +341,6 @@ core_secret_cb (void *cls, size_t secret_size) { struct SelectChallengeContext *sctx = cls; - enum TALER_ErrorCode ec; sctx->r = NULL; if (ANASTASIS_RS_SUCCESS == rc) @@ -311,11 +372,9 @@ core_secret_cb (void *cls, sctx_free (sctx); return; } - ec = update_state_by_error (sctx->state, - rc); - sctx->cb (sctx->cb_cls, - ec, - sctx->state); + fail_by_error (sctx->cb, + sctx->cb_cls, + rc); sctx_free (sctx); } @@ -399,7 +458,7 @@ find_challenge_in_ri (json_t *state, /** - * Find challenge of @a uuid in @a state under "cs". + * Find challenge of @a uuid in @a state under "challenges". * * @param state the state to search * @param uuid the UUID to search for @@ -412,7 +471,7 @@ find_challenge_in_cs (json_t *state, json_t *rd = json_object_get (state, "recovery_document"); json_t *cs = json_object_get (rd, - "cs"); + "challenges"); json_t *c; size_t off; @@ -451,7 +510,7 @@ find_challenge_in_cs (json_t *state, * @param csr response details */ static void -answer_feedback_cb ( +start_feedback_cb ( void *cls, const struct ANASTASIS_ChallengeStartResponse *csr) { @@ -480,115 +539,50 @@ answer_feedback_cb ( } switch (csr->cs) { - case ANASTASIS_CHALLENGE_STATUS_SOLVED: + case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED: { - json_t *rd; - - rd = ANASTASIS_recovery_serialize (sctx->r); - if (NULL == rd) - { - GNUNET_break (0); - set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_ERROR); - sctx->cb (sctx->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - sctx->state); - sctx_free (sctx); - return; - } - GNUNET_assert (0 == - json_object_set_new (sctx->state, - "recovery_document", - rd)); - } - { - json_t *solved; + json_t *instructions; + char *hint; - solved = GNUNET_JSON_PACK ( + GNUNET_asprintf (&hint, + _ ("Required TAN can be found in `%s'"), + csr->details.tan_filename); + instructions = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "solved")); + "code-in-file"), + GNUNET_JSON_pack_string ("filename", + csr->details.tan_filename), + GNUNET_JSON_pack_string ("display_hint", + hint)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, - solved)); + instructions)); } - /* Delay reporting challenge success, as we MAY still - also see a secret recovery success (and we can only - call the callback once) */ - sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved, - sctx); + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED: { json_t *instructions; - const char *mime; - - mime = csr->details.open_challenge.content_type; - if (NULL != mime) - { - if ( (0 == strcasecmp (mime, - "text/plain")) || - (0 == strcasecmp (mime, - "text/utf8")) ) - { - char *s = GNUNET_strndup (csr->details.open_challenge.body, - csr->details.open_challenge.body_size); - - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "hint"), - GNUNET_JSON_pack_string ("hint", - s), - GNUNET_JSON_pack_uint64 ("http_status", - (json_int_t) csr->details.open_challenge. - http_status)); - GNUNET_free (s); - } - else if (0 == strcasecmp (mime, - "application/json")) - { - json_t *body; + char *hint; - body = json_loadb (csr->details.open_challenge.body, - csr->details.open_challenge.body_size, - JSON_REJECT_DUPLICATES, - NULL); - if (NULL == body) - { - GNUNET_break_op (0); - mime = NULL; - } - else - { - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "details"), - GNUNET_JSON_pack_object_steal ("details", - body), - GNUNET_JSON_pack_uint64 ("http_status", - csr->details.open_challenge.http_status)); - } - } - else - { - /* unexpected / unsupported mime type */ - mime = NULL; - } - } - if (NULL == mime) - { - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "body"), - GNUNET_JSON_pack_data_varsize ("body", - csr->details.open_challenge.body, - csr->details.open_challenge.body_size), - GNUNET_JSON_pack_uint64 ("http_status", - csr->details.open_challenge.http_status), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("mime_type", - mime))); - } + GNUNET_asprintf (&hint, + _ ("TAN code was sent to `%s'"), + csr->details.tan_address_hint); + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "send-to-address"), + GNUNET_JSON_pack_string ("address_hint", + csr->details.tan_address_hint), + GNUNET_JSON_pack_string ("display_hint", + hint)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -601,20 +595,24 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION: + + case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT: { - json_t *redir; + json_t *instructions; + char *hint; - redir = GNUNET_JSON_PACK ( + GNUNET_asprintf (&hint, + _ ("TAN code already sent.")); + instructions = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "redirect"), - GNUNET_JSON_pack_string ("redirect_url", - csr->details.redirect_url)); - GNUNET_assert (NULL != redir); + "send-to-address"), + GNUNET_JSON_pack_string ("display_hint", + hint)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, - redir)); + instructions)); } set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); @@ -623,21 +621,29 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED: + + case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED: { json_t *pay; + char *hint; + GNUNET_asprintf (&hint, + _ ("Taler payment to `%s' required"), + csr->details.payment_required.taler_pay_uri); pay = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "payment"), - GNUNET_JSON_pack_string ("taler_pay_uri", - csr->details.payment_required. - taler_pay_uri), + "taler-payment"), + GNUNET_JSON_pack_string ( + "taler_pay_uri", + csr->details.payment_required.taler_pay_uri), GNUNET_JSON_pack_string ("provider", cd->provider_url), + GNUNET_JSON_pack_string ("display_hint", + hint), GNUNET_JSON_pack_data_auto ( "payment_secret", &csr->details.payment_required.payment_secret)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -663,7 +669,7 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE: + case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE: { json_t *err; @@ -671,10 +677,9 @@ answer_feedback_cb ( GNUNET_JSON_pack_string ("state", "server-failure"), GNUNET_JSON_pack_uint64 ("http_status", - csr->details.server_failure. - http_status), + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - csr->details.server_failure.ec)); + csr->ec)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -683,17 +688,19 @@ answer_feedback_cb ( set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - csr->details.server_failure.ec, + csr->ec, sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN: + case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN: { json_t *err; err = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", "truth-unknown"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); GNUNET_assert (0 == @@ -708,15 +715,242 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED: + case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED: + { + json_t *reply; + json_t *c; + char *hint; + + c = find_challenge_in_cs (sctx->state, + &cd->uuid); + if (NULL == c) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + sctx_free (sctx); + return; + } + GNUNET_assert (0 == + json_object_set_new (c, + "async", + json_true ())); + GNUNET_assert ( + 0 == + json_object_set_new ( + c, + "answer-pin", + json_integer ( + csr->details.bank_transfer_required.answer_code))); + GNUNET_asprintf (&hint, + _ ("Wire %s to %s (%s) with subject %s\n"), + TALER_amount2s ( + &csr->details.bank_transfer_required.amount), + csr->details.bank_transfer_required.target_iban, + csr->details.bank_transfer_required.target_business_name, + csr->details.bank_transfer_required.wire_transfer_subject); + reply = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "iban-instructions"), + GNUNET_JSON_pack_string ( + "target_iban", + csr->details.bank_transfer_required.target_iban), + GNUNET_JSON_pack_string ( + "display_hint", + hint), + GNUNET_JSON_pack_string ( + "target_business_name", + csr->details.bank_transfer_required.target_business_name), + GNUNET_JSON_pack_string ( + "wire_transfer_subject", + csr->details.bank_transfer_required.wire_transfer_subject), + TALER_JSON_pack_amount ( + "challenge_amount", + &csr->details.bank_transfer_required.amount)); + GNUNET_free (hint); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + reply)); + } + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "selected_challenge_uuid", + GNUNET_JSON_from_data_auto ( + &cd->uuid))); + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + } + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + sctx_free (sctx); +} + + +/** + * Defines a callback for the response status for a challenge answer + * operation. + * + * @param cls a `struct SelectChallengeContext *` + * @param csr response details + */ +static void +answer_feedback_cb ( + void *cls, + const struct ANASTASIS_ChallengeAnswerResponse *csr) +{ + struct SelectChallengeContext *sctx = cls; + const struct ANASTASIS_ChallengeDetails *cd; + char uuid[sizeof (cd->uuid) * 2]; + char *end; + json_t *feedback; + + cd = ANASTASIS_challenge_get_details (csr->challenge); + end = GNUNET_STRINGS_data_to_string (&cd->uuid, + sizeof (cd->uuid), + uuid, + sizeof (uuid)); + GNUNET_assert (NULL != end); + *end = '\0'; + feedback = json_object_get (sctx->state, + "challenge_feedback"); + if (NULL == feedback) + { + feedback = json_object (); + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "challenge_feedback", + feedback)); + } + switch (csr->cs) + { + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED: + { + json_t *rd; + + rd = ANASTASIS_recovery_serialize (sctx->r); + if (NULL == rd) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unable to serialize recovery state"); + sctx_free (sctx); + return; + } + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "recovery_document", + rd)); + } + { + json_t *solved; + + solved = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "solved")); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + solved)); + } + /* Delay reporting challenge success, as we MAY still + also see a secret recovery success (and we can only + call the callback once) */ + sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved, + sctx); + return; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER: + { + json_t *instructions; + + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "incorrect-answer"), + GNUNET_JSON_pack_uint64 ("error_code", + csr->ec)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + instructions)); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED: + { + json_t *pay; + char *hint; + + GNUNET_asprintf (&hint, + _ ("Taler payment to `%s' required"), + csr->details.payment_required.taler_pay_uri); + pay = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "taler-payment"), + GNUNET_JSON_pack_string ( + "taler_pay_uri", + csr->details.payment_required.taler_pay_uri), + GNUNET_JSON_pack_string ( + "display_hint", + hint), + GNUNET_JSON_pack_string ("provider", + cd->provider_url), + GNUNET_JSON_pack_data_auto ( + "payment_secret", + &csr->details.payment_required.payment_secret)); + GNUNET_free (hint); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + pay)); + } + /* Remember payment secret for later (once application claims it paid) */ + { + json_t *challenge = find_challenge_in_ri (sctx->state, + &cd->uuid); + + GNUNET_assert (NULL != challenge); + GNUNET_assert (0 == + json_object_set_new ( + challenge, + "payment_secret", + GNUNET_JSON_from_data_auto ( + &csr->details.payment_required.payment_secret))); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE: { json_t *err; err = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "rate-limit-exceeded"), + "server-failure"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); + csr->ec)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -725,119 +959,71 @@ answer_feedback_cb ( set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, + csr->ec, sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN: { json_t *err; err = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "authentication-timeout"), + "truth-unknown"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT)); + TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, err)); } - GNUNET_break_op (0); set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT, + TALER_EC_ANASTASIS_TRUTH_UNKNOWN, sctx->state); sctx_free (sctx); return; - - case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED: { - const json_t *body = csr->details.external_challenge; - const char *method; - json_t *details; - bool is_async = false; - uint64_t code = 0; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("method", - &method), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("async", - &is_async)), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint64 ("answer_code", - &code)), - GNUNET_JSON_spec_json ("details", - &details), - GNUNET_JSON_spec_end () - }; - json_t *reply; - - if (GNUNET_OK != - GNUNET_JSON_parse (body, - spec, - NULL, NULL)) - { - json_t *err; - - GNUNET_break_op (0); - err = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "server-failure"), - GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_GENERIC_REPLY_MALFORMED)); - GNUNET_assert (0 == - json_object_set_new (feedback, - uuid, - err)); - return; - } - if (is_async) - { - json_t *c = find_challenge_in_cs (sctx->state, - &cd->uuid); - - if (NULL == c) - { - GNUNET_break (0); - set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_ERROR); - sctx->cb (sctx->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - sctx->state); - sctx_free (sctx); - return; - } - GNUNET_assert (0 == - json_object_set_new (c, - "async", - json_true ())); - GNUNET_assert (0 == - json_object_set_new (c, - "answer-pin", - json_integer (code))); - } - reply = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "external-instructions"), - GNUNET_JSON_pack_string ("method", - method), - GNUNET_JSON_pack_object_incref ("details", - details)); - GNUNET_JSON_parse_free (spec); + json_t *err; + char *hint; + + GNUNET_asprintf ( + &hint, + _ ("exceeded limit of %llu attempts in %s"), + (unsigned long long) csr->details.rate_limit_exceeded.request_limit, + GNUNET_TIME_relative2s ( + csr->details.rate_limit_exceeded.request_frequency, + true)); + err = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "state", + "rate-limit-exceeded"), + GNUNET_JSON_pack_string ( + "display_hint", + hint), + GNUNET_JSON_pack_uint64 ( + "request_limit", + csr->details.rate_limit_exceeded.request_limit), + GNUNET_JSON_pack_time_rel ( + "request_frequency", + csr->details.rate_limit_exceeded.request_frequency), + GNUNET_JSON_pack_uint64 ( + "error_code", + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); + GNUNET_free (hint); GNUNET_assert (0 == json_object_set_new (feedback, uuid, - reply)); + err)); } - json_object_set_new (sctx->state, - "selected_challenge_uuid", - GNUNET_JSON_from_data_auto (&cd->uuid)); set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - TALER_EC_NONE, + TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, sctx->state); sctx_free (sctx); return; @@ -873,7 +1059,8 @@ solve_challenge_cb (void *cls, struct GNUNET_JSON_Specification tspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_end () }; struct GNUNET_JSON_Specification pspec[] = { @@ -1138,21 +1325,19 @@ solve_challenge_cb (void *cls, sctx_free (sctx); return; } - ret = ANASTASIS_challenge_start (ci, - psp, - timeout, - &hashed_answer, - &answer_feedback_cb, - sctx); + ret = ANASTASIS_challenge_answer3 (ci, + psp, + timeout, + &hashed_answer, + &answer_feedback_cb, + sctx); } else { /* no answer provided */ ret = ANASTASIS_challenge_start (ci, psp, - timeout, - NULL, /* no answer */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } } @@ -1272,9 +1457,7 @@ pay_challenge_cb (void *cls, { ret = ANASTASIS_challenge_start (ci, &sctx->ps, - sctx->timeout, - NULL, /* no answer yet */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } if (GNUNET_OK != ret) @@ -1468,7 +1651,8 @@ pay_challenge (json_t *state, struct GNUNET_JSON_Specification aspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_fixed_auto ("payment_secret", &sctx->ps), GNUNET_JSON_spec_end () @@ -1566,7 +1750,8 @@ select_challenge_cb (void *cls, struct GNUNET_JSON_Specification tspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("timeout", - &timeout)), + &timeout), + NULL), GNUNET_JSON_spec_end () }; struct GNUNET_JSON_Specification pspec[] = { @@ -1684,10 +1869,13 @@ select_challenge_cb (void *cls, json_object_set_new (sctx->state, "selected_challenge_uuid", GNUNET_JSON_from_data_auto (&cd->uuid))); - if (0 == strcmp ("question", - cd->type)) + if ( (0 == strcmp ("question", + cd->type)) || + (0 == strcmp ("totp", + cd->type)) ) { - /* security question, immediately request user to answer it */ + /* security question or TOTP: + immediately request user to answer it */ set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); sctx->cb (sctx->cb_cls, @@ -1718,9 +1906,7 @@ select_challenge_cb (void *cls, { ret = ANASTASIS_challenge_start (ci, psp, - timeout, - NULL, /* no answer */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } } @@ -1856,256 +2042,15 @@ back_challenge_solving (json_t *state, /** - * The user wants us to change the policy version. Download another version. - * - * @param[in] state we are in - * @param arguments our arguments with the solution - * @param cb functiont o call with the new state - * @param cb_cls closure for @a cb - * @return handle to cancel challenge selection step - */ -static struct ANASTASIS_ReduxAction * -change_version (json_t *state, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls) -{ - uint64_t version; - const char *provider_url; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("version", - &version), - GNUNET_JSON_spec_string ("provider_url", - &provider_url), - GNUNET_JSON_spec_end () - }; - json_t *ia; - json_t *args; - struct ANASTASIS_ReduxAction *ra; - - if (GNUNET_OK != - GNUNET_JSON_parse (arguments, - spec, - NULL, NULL)) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'version' invalid"); - return NULL; - } - GNUNET_assert (NULL != provider_url); - ia = json_object_get (state, - "identity_attributes"); - if (NULL == ia) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'identity_attributes' missing"); - return NULL; - } - args = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("version", - version), - GNUNET_JSON_pack_object_incref ("identity_attributes", - (json_t *) ia), - GNUNET_JSON_pack_string ("provider_url", - provider_url)); - if (NULL == args) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - NULL); - return NULL; - } - ra = ANASTASIS_REDUX_recovery_challenge_begin_ (state, - args, - cb, - cb_cls); - json_decref (args); - return ra; -} - - -/** - * DispatchHandler/Callback function which is called for a - * "next" action in "secret_selecting" state. - * - * @param state state to operate on - * @param arguments arguments to use for operation on state - * @param cb callback to call during/after operation - * @param cb_cls callback closure - * @return NULL - */ -static struct ANASTASIS_ReduxAction * -done_secret_selecting (json_t *state, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls) -{ - const json_t *ri; - - ri = json_object_get (state, - "recovery_information"); - if ( (NULL == ri) || - (NULL == json_object_get (ri, - "challenges")) ) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE, - "no valid version selected"); - return NULL; - } - set_state (state, - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); - cb (cb_cls, - TALER_EC_NONE, - state); - return NULL; -} - - -/** - * Signature of callback function that implements a state transition. - * - * @param state current state - * @param arguments arguments for the state transition - * @param cb function to call when done - * @param cb_cls closure for @a cb - */ -typedef struct ANASTASIS_ReduxAction * -(*DispatchHandler)(json_t *state, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls); - - -struct ANASTASIS_ReduxAction * -ANASTASIS_recovery_action_ (json_t *state, - const char *action, - const json_t *arguments, - ANASTASIS_ActionCallback cb, - void *cb_cls) -{ - struct Dispatcher - { - enum ANASTASIS_RecoveryState recovery_state; - const char *recovery_action; - DispatchHandler fun; - } dispatchers[] = { - { - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, - "change_version", - &change_version - }, - { - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, - "next", - &done_secret_selecting - }, - { - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, - "back", - &ANASTASIS_back_generic_decrement_ - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, - "select_challenge", - &select_challenge - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, - "poll", - &poll_challenges - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, - "back", - &ANASTASIS_back_generic_decrement_ - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, - "pay", - &pay_challenge - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, - "back", - &ANASTASIS_back_generic_decrement_ - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, - "solve_challenge", - &solve_challenge - }, - { - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, - "back", - &back_challenge_solving - }, - { ANASTASIS_RECOVERY_STATE_ERROR, NULL, NULL } - }; - const char *s = json_string_value (json_object_get (state, - "recovery_state")); - enum ANASTASIS_RecoveryState rs; - - GNUNET_assert (NULL != s); - rs = ANASTASIS_recovery_state_from_string_ (s); - if (ANASTASIS_RECOVERY_STATE_ERROR == rs) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "'recovery_state' field invalid"); - return NULL; - } - for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) - { - if ( (rs == dispatchers[i].recovery_state) && - (0 == strcmp (action, - dispatchers[i].recovery_action)) ) - { - return dispatchers[i].fun (state, - arguments, - cb, - cb_cls); - } - } - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, - action); - return NULL; -} - - -/** - * State for a "recover secret" CMD. - */ -struct RecoverSecretState; - - -/** * State for a "policy download" as part of a recovery operation. */ struct PolicyDownloadEntry { /** - * Kept in a DLL. - */ - struct PolicyDownloadEntry *prev; - - /** - * Kept in a DLL. + * Redux action handle associated with this state. */ - struct PolicyDownloadEntry *next; + struct ANASTASIS_ReduxAction ra; /** * Backend we are querying. @@ -2113,289 +2058,46 @@ struct PolicyDownloadEntry char *backend_url; /** - * Salt to be used to derive the id for this provider - */ - struct ANASTASIS_CRYPTO_ProviderSaltP salt; - - /** - * Context we operate in. - */ - struct RecoverSecretState *rss; - - /** * The /policy GET operation handle. */ struct ANASTASIS_Recovery *recovery; -}; - - -/** - * Entry in the list of all known applicable Anastasis providers. - * Used to wait for it to complete downloading /config. - */ -struct RecoveryStartStateProviderEntry -{ - /** - * Kept in a DLL. - */ - struct RecoveryStartStateProviderEntry *next; - - /** - * Kept in a DLL. - */ - struct RecoveryStartStateProviderEntry *prev; - - /** - * Main operation this entry is part of. - */ - struct RecoverSecretState *rss; - /** - * Resulting provider information, NULL if not (yet) available. - */ - json_t *istate; - - /** - * Ongoing reducer action to obtain /config, NULL if completed. - */ - struct ANASTASIS_ReduxAction *ra; - - /** - * Final result of the operation (once completed). - */ - enum TALER_ErrorCode ec; -}; - - -/** - * State for a "recover secret" CMD. - */ -struct RecoverSecretState -{ - - /** - * Redux action handle associated with this state. - */ - struct ANASTASIS_ReduxAction ra; - - /** - * Head of list of provider /config operations we are doing. - */ - struct RecoveryStartStateProviderEntry *pe_head; - - /** - * Tail of list of provider /config operations we are doing. - */ - struct RecoveryStartStateProviderEntry *pe_tail; - - /** - * Identification data from the user - */ - json_t *id_data; - - /** - * Head of DLL of policy downloads. - */ - struct PolicyDownloadEntry *pd_head; - - /** - * Tail of DLL of policy downloads. - */ - struct PolicyDownloadEntry *pd_tail; - - /** - * Reference to our state. - */ - json_t *state; - - /** - * callback to call during/after operation + * Function to call with the result. */ ANASTASIS_ActionCallback cb; /** - * closure for action callback @e cb. + * Closure for @e cb. */ void *cb_cls; /** - * Set if recovery must be done with this provider. - */ - char *provider_url; - - /** - * version of the recovery document to request. - */ - unsigned int version; - - /** - * Number of provider /config operations in @e ba_head that - * are still awaiting completion. + * State we are using. */ - unsigned int pending; + json_t *state; - /** - * Is @e version set? - */ - bool have_version; }; /** - * Function to free a `struct RecoverSecretState` + * Free @a cls data structure. * - * @param cls must be a `struct RecoverSecretState` + * @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *` */ static void -free_rss (void *cls) +free_pd (void *cls) { - struct RecoverSecretState *rss = cls; - struct PolicyDownloadEntry *pd; - struct RecoveryStartStateProviderEntry *pe; + struct PolicyDownloadEntry *pd = cls; - while (NULL != (pe = rss->pe_head)) - { - GNUNET_CONTAINER_DLL_remove (rss->pe_head, - rss->pe_tail, - pe); - ANASTASIS_redux_action_cancel (pe->ra); - rss->pending--; - GNUNET_free (pe); - } - while (NULL != (pd = rss->pd_head)) + if (NULL != pd->recovery) { - GNUNET_CONTAINER_DLL_remove (rss->pd_head, - rss->pd_tail, - pd); - if (NULL != pd->recovery) - { - ANASTASIS_recovery_abort (pd->recovery); - pd->recovery = NULL; - } - GNUNET_free (pd->backend_url); - GNUNET_free (pd); + ANASTASIS_recovery_abort (pd->recovery); + pd->recovery = NULL; } - json_decref (rss->state); - json_decref (rss->id_data); - GNUNET_assert (0 == rss->pending); - GNUNET_free (rss->provider_url); - GNUNET_free (rss); -} - - -/** - * This function is called whenever the recovery process ends. - * In this case, that should not be possible as this callback - * is used before we even begin with the challenges. So if - * we are called, it is because of some fatal error. - * - * @param cls a `struct PolicyDownloadEntry` - * @param rc error code - * @param secret contains the core secret which is passed to the user - * @param secret_size defines the size of the core secret - */ -static void -core_early_secret_cb (void *cls, - enum ANASTASIS_RecoveryStatus rc, - const void *secret, - size_t secret_size) -{ - struct PolicyDownloadEntry *pd = cls; - struct RecoverSecretState *rss = pd->rss; - enum TALER_ErrorCode ec; - - pd->recovery = NULL; - GNUNET_assert (NULL == secret); - GNUNET_CONTAINER_DLL_remove (rss->pd_head, - rss->pd_tail, - pd); GNUNET_free (pd->backend_url); + json_decref (pd->state); GNUNET_free (pd); - if (NULL != rss->pd_head) - return; /* wait for another one */ - /* all failed! report failure! */ - GNUNET_assert (ANASTASIS_RS_SUCCESS != rc); - ec = update_state_by_error (rss->state, - rc); - rss->cb (rss->cb_cls, - ec, - rss->state); - rss->cb = NULL; - free_rss (rss); -} - - -/** - * Determine recovery @a cost of solving a challenge of type @a type - * at @a provider_url by inspecting @a state. - * - * @param state the state to inspect - * @param provider_url the provider to lookup config info from - * @param type the method to lookup the cost of - * @param[out] cost the recovery cost to return - * @return #GNUNET_OK on success, #GNUNET_NO if not found, #GNUNET_SYSERR on state error - */ -static int -lookup_cost (const json_t *state, - const char *provider_url, - const char *type, - struct TALER_Amount *cost) -{ - const json_t *providers; - const json_t *provider; - const json_t *methods; - - providers = json_object_get (state, - "authentication_providers"); - if (NULL == providers) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - provider = json_object_get (providers, - provider_url); - if (NULL == provider) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - methods = json_object_get (provider, - "methods"); - if ( (NULL == methods) || - (! json_is_array (methods)) ) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - { - size_t index; - json_t *method; - - json_array_foreach (methods, index, method) { - const char *t; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("type", - &t), - TALER_JSON_spec_amount_any ("usage_fee", - cost), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (method, - spec, - NULL, NULL)) - { - GNUNET_break (0); - continue; - } - if (0 == strcmp (t, - type)) - return GNUNET_OK; - } - } - return GNUNET_NO; /* not found */ } @@ -2404,40 +2106,34 @@ lookup_cost (const json_t *state, * allow the user to specify alternative providers and/or policy * versions. * - * @param[in] rss state to fail with the policy download + * @param[in] pd state to fail with the policy download * @param offline true of the reason to show is that all providers * were offline / did not return a salt to us */ static void -return_no_policy (struct RecoverSecretState *rss, +return_no_policy (struct PolicyDownloadEntry *pd, bool offline) { - json_t *msg; + enum TALER_ErrorCode ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED; + const char *detail = (offline) + ? "could not contact provider (offline)" + : "provider does not know this policy"; + json_t *estate; GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No provider online, need user to manually specify providers!\n"); - msg = GNUNET_JSON_PACK ( + "Provider offline!\n"); + estate = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", + detail)), + GNUNET_JSON_pack_uint64 ("code", + ec), GNUNET_JSON_pack_string ("hint", - offline - ? "could not contact provider" - : "provider does not know you"), - GNUNET_JSON_pack_bool ("offline", - offline)); - GNUNET_assert (0 == - json_object_set_new (rss->state, - "recovery_error", - msg)); - /* In case there are old ones, remove them! */ - (void) json_object_del (rss->state, - "recovery_document"); - (void) json_object_del (rss->state, - "recovery_information"); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); - rss->cb (rss->cb_cls, - TALER_EC_NONE, - rss->state); - free_rss (rss); + TALER_ErrorCode_get_hint (ec))); + pd->cb (pd->cb_cls, + ec, + estate); + free_pd (pd); } @@ -2450,7 +2146,7 @@ return_no_policy (struct RecoverSecretState *rss, * cancel all of the others, passing the obtained recovery information * back to the user. * - * @param cls closure for the callback + * @param cls closure for the callback with a `struct PolicyDownloadEntry *` * @param ri recovery information struct which contains the policies */ static void @@ -2458,7 +2154,6 @@ policy_lookup_cb (void *cls, const struct ANASTASIS_RecoveryInformation *ri) { struct PolicyDownloadEntry *pd = cls; - struct RecoverSecretState *rss = pd->rss; json_t *policies; json_t *challenges; json_t *recovery_information; @@ -2466,16 +2161,10 @@ policy_lookup_cb (void *cls, if (NULL == ri) { /* Woopsie, failed hard. */ - GNUNET_CONTAINER_DLL_remove (rss->pd_head, - rss->pd_tail, - pd); ANASTASIS_recovery_abort (pd->recovery); GNUNET_free (pd->backend_url); GNUNET_free (pd); - if (NULL != rss->pd_head) - return; /* wait for another one */ - /* all failed! report failure! */ - return_no_policy (rss, + return_no_policy (pd, false); return; } @@ -2514,37 +2203,15 @@ policy_lookup_cb (void *cls, struct ANASTASIS_Challenge *c = ri->cs[i]; const struct ANASTASIS_ChallengeDetails *cd; json_t *cj; - struct TALER_Amount cost; - int ret; cd = ANASTASIS_challenge_get_details (c); - ret = lookup_cost (rss->state, - cd->provider_url, - cd->type, - &cost); - if (GNUNET_SYSERR == ret) - { - json_decref (challenges); - json_decref (policies); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_ERROR); - ANASTASIS_redux_fail_ (rss->cb, - rss->cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "failed to 'lookup_cost'"); - free_rss (rss); - return; - } - cj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("uuid", &cd->uuid), - TALER_JSON_pack_amount ("cost", - (GNUNET_NO == ret) - ? NULL - : &cost), GNUNET_JSON_pack_string ("type", cd->type), + GNUNET_JSON_pack_string ("uuid-display", + ANASTASIS_CRYPTO_uuid2s (&cd->uuid)), GNUNET_JSON_pack_string ("instructions", cd->instructions)); GNUNET_assert (0 == @@ -2564,7 +2231,7 @@ policy_lookup_cb (void *cls, GNUNET_JSON_pack_uint64 ("version", ri->version)); GNUNET_assert (0 == - json_object_set_new (rss->state, + json_object_set_new (pd->state, "recovery_information", recovery_information)); { @@ -2574,229 +2241,387 @@ policy_lookup_cb (void *cls, if (NULL == rd) { GNUNET_break (0); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_ERROR); - rss->cb (rss->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - rss->state); - free_rss (rss); + ANASTASIS_redux_fail_ (pd->cb, + pd->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unable to serialize recovery state"); + free_pd (pd); return; } GNUNET_assert (0 == - json_object_set_new (rss->state, + json_object_set_new (pd->state, "recovery_document", rd)); } - /* In case there is an old error remove it! */ - (void) json_object_del (rss->state, - "recovery_error"); - set_state (rss->state, - ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); - rss->cb (rss->cb_cls, - TALER_EC_NONE, - rss->state); - free_rss (rss); + set_state (pd->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); + pd->cb (pd->cb_cls, + TALER_EC_NONE, + pd->state); + free_pd (pd); } /** - * Try to launch recovery at provider @a provider_url with config @a p_cfg. + * This function is called whenever the recovery process ends. + * In this case, that should not be possible as this callback + * is used before we even begin with the challenges. So if + * we are called, it is because of some fatal error. * - * @param[in,out] rss recovery context - * @param provider_url base URL of the provider to try - * @param p_cfg configuration of the provider - * @return true if a recovery was launched + * @param cls a `struct PolicyDownloadEntry` + * @param rc error code + * @param secret contains the core secret which is passed to the user + * @param secret_size defines the size of the core secret */ -static bool -launch_recovery (struct RecoverSecretState *rss, - const char *provider_url, - const json_t *p_cfg) +static void +core_early_secret_cb (void *cls, + enum ANASTASIS_RecoveryStatus rc, + const void *secret, + size_t secret_size) { - struct PolicyDownloadEntry *pd = GNUNET_new (struct PolicyDownloadEntry); - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("salt", - &pd->salt), - GNUNET_JSON_spec_end () - }; + struct PolicyDownloadEntry *pd = cls; - if (MHD_HTTP_OK != - json_integer_value (json_object_get (p_cfg, - "http_status"))) - return false; /* skip providers that are down */ - if (GNUNET_OK != - GNUNET_JSON_parse (p_cfg, - spec, - NULL, NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No salt for `%s', provider offline?\n", - provider_url); - GNUNET_free (pd); - return false; - } - pd->backend_url = GNUNET_strdup (provider_url); - pd->rss = rss; - pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_, - rss->id_data, - rss->have_version - ? rss->version - : 0, - pd->backend_url, - &pd->salt, - &policy_lookup_cb, - pd, - &core_early_secret_cb, - pd); - if (NULL != pd->recovery) - { - GNUNET_CONTAINER_DLL_insert (rss->pd_head, - rss->pd_tail, - pd); - return true; - } - GNUNET_free (pd->backend_url); - GNUNET_free (pd); - return false; + pd->recovery = NULL; + GNUNET_assert (NULL == secret); + GNUNET_assert (ANASTASIS_RS_SUCCESS != rc); + fail_by_error (pd->cb, + pd->cb_cls, + rc); + free_pd (pd); } /** - * We finished downloading /config from all providers, merge - * into the main state, trigger the continuation and free our - * state. + * DispatchHandler/Callback function which is called for a + * "next" action in "secret_selecting" state. * - * @param[in] rss main state to merge into + * @param state state to operate on + * @param arguments arguments to use for operation on state + * @param cb callback to call during/after operation + * @param cb_cls callback closure + * @return NULL */ -static void -providers_complete (struct RecoverSecretState *rss) +static struct ANASTASIS_ReduxAction * +done_secret_selecting (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) { - bool launched = false; - struct RecoveryStartStateProviderEntry *pe; - json_t *tlist; + uint32_t mask; + const json_t *pa; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("attribute_mask", + &mask), + GNUNET_JSON_spec_array_const ("providers", + &pa), + GNUNET_JSON_spec_end () + }; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_fixed_auto ("provider_salt", + &provider_salt), + GNUNET_JSON_spec_end () + }; + json_t *p_cfg; + json_t *id_data; + const json_t *providers; - tlist = json_object_get (rss->state, - "authentication_providers"); - if (NULL == tlist) + if (GNUNET_OK != + GNUNET_JSON_parse (arguments, + spec, + NULL, NULL)) { - tlist = json_object (); - GNUNET_assert (NULL != tlist); - GNUNET_assert (0 == - json_object_set_new (rss->state, - "authentication_providers", - tlist)); + GNUNET_break (0); + json_dumpf (arguments, + stderr, + JSON_INDENT (2)); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + NULL); + return NULL; } - while (NULL != (pe = rss->pe_head)) + providers = json_object_get (state, + "authentication_providers"); + if ( (NULL == providers) || + (! json_is_object (providers)) ) { - json_t *provider_list; - - GNUNET_CONTAINER_DLL_remove (rss->pe_head, - rss->pe_tail, - pe); - provider_list = json_object_get (pe->istate, - "authentication_providers"); - /* merge provider_list into tlist (overriding existing entries) */ - if (NULL != provider_list) - { - const char *url; - json_t *value; - - json_object_foreach (provider_list, url, value) { - GNUNET_assert (0 == - json_object_set (tlist, - url, - value)); - } - } - json_decref (pe->istate); - GNUNET_free (pe); + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'authentication_providers' missing"); + return NULL; } - /* now iterate over providers and begin downloading */ - if (NULL != rss->provider_url) { - json_t *p_cfg; - - p_cfg = json_object_get (tlist, - rss->provider_url); - if (NULL != p_cfg) - launched = launch_recovery (rss, - rss->provider_url, - p_cfg); - } - else - { - json_t *p_cfg; + size_t poff; + json_t *pe; + uint64_t version; const char *provider_url; - json_object_foreach (tlist, provider_url, p_cfg) + json_array_foreach (pa, poff, pe) { - launched |= launch_recovery (rss, - provider_url, - p_cfg); + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_uint64 ("version", + &version), + GNUNET_JSON_spec_string ("url", + &provider_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pe, + ispec, + NULL, NULL)) + { + GNUNET_break (0); + json_dumpf (pe, + stderr, + JSON_INDENT (2)); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + NULL); + return NULL; + } + + p_cfg = json_object_get (providers, + provider_url); + if (MHD_HTTP_OK != + json_integer_value (json_object_get (p_cfg, + "http_status"))) + continue; + if (GNUNET_OK != + GNUNET_JSON_parse (p_cfg, + pspec, + NULL, NULL)) + { + GNUNET_break (0); /* should be impossible for well-formed state */ + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "Salt unknown for selected provider"); + return NULL; + } + id_data = json_object_get (state, + "identity_attributes"); + if (NULL == id_data) + { + GNUNET_break (0); /* should be impossible for well-formed state */ + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'identity_attributes' missing"); + return NULL; + } + { + struct PolicyDownloadEntry *pd + = GNUNET_new (struct PolicyDownloadEntry); + + pd->cb = cb; + pd->cb_cls = cb_cls; + pd->state = json_incref (state); + pd->backend_url = GNUNET_strdup (provider_url); + pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_, + id_data, + version, + pd->backend_url, + &provider_salt, + &policy_lookup_cb, + pd, + &core_early_secret_cb, + pd); + if (NULL == pd->recovery) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, + NULL); + GNUNET_free (pd->backend_url); + json_decref (pd->state); + GNUNET_free (pd); + return NULL; + } + pd->ra.cleanup = &free_pd; + pd->ra.cleanup_cls = pd; + return &pd->ra; + } } } - if (! launched) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No provider online, need user to specify different provider!\n"); - return_no_policy (rss, - true); - return; - } + + /* no provider worked */ + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + "selected provider is not online"); + return NULL; } /** - * Function called when the complete information about a provider - * was added to @a new_state. + * The user wants us to add another provider. Download /config. * - * @param cls a `struct RecoveryStartStateProviderEntry` - * @param error error code - * @param new_state resulting new state + * @param[in] state we are in + * @param arguments our arguments with the solution + * @param cb function to call with the new state + * @param cb_cls closure for @a cb + * @return handle to cancel challenge selection step */ -static void -provider_added_cb (void *cls, - enum TALER_ErrorCode error, - json_t *new_state) +static struct ANASTASIS_ReduxAction * +add_provider (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) { - struct RecoveryStartStateProviderEntry *pe = cls; - - pe->ra = NULL; - pe->istate = json_incref (new_state); - pe->ec = error; - pe->rss->pending--; - if (0 == pe->rss->pending) - providers_complete (pe->rss); + const char *provider_url; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("provider_url", + &provider_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (arguments, + spec, + NULL, NULL)) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + NULL); + return NULL; + } + return ANASTASIS_REDUX_add_provider_to_state_ (provider_url, + state, + cb, + cb_cls); } /** - * Start to query provider for recovery document. + * Signature of callback function that implements a state transition. * - * @param[in,out] rss overall recovery state - * @param provider_url base URL of the provider to query + * @param state current state + * @param arguments arguments for the state transition + * @param cb function to call when done + * @param cb_cls closure for @a cb */ -static void -begin_query_provider (struct RecoverSecretState *rss, - const char *provider_url) +typedef struct ANASTASIS_ReduxAction * +(*DispatchHandler)(json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls); + + +struct ANASTASIS_ReduxAction * +ANASTASIS_recovery_action_ (json_t *state, + const char *action, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) { - struct RecoveryStartStateProviderEntry *pe; - json_t *istate; - - pe = GNUNET_new (struct RecoveryStartStateProviderEntry); - pe->rss = rss; - istate = json_object (); - GNUNET_assert (NULL != istate); - GNUNET_CONTAINER_DLL_insert (rss->pe_head, - rss->pe_tail, - pe); - pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url, - istate, - &provider_added_cb, - pe); - json_decref (istate); - if (NULL != pe->ra) - rss->pending++; + struct Dispatcher + { + enum ANASTASIS_RecoveryState recovery_state; + const char *recovery_action; + DispatchHandler fun; + } dispatchers[] = { + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "add_provider", + &add_provider + }, + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "poll_providers", + &ANASTASIS_REDUX_poll_providers_ + }, + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "select_version", + &done_secret_selecting + }, + { + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING, + "back", + &ANASTASIS_back_generic_decrement_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "select_challenge", + &select_challenge + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "sync_providers", + &ANASTASIS_REDUX_sync_providers_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "poll", + &poll_challenges + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, + "back", + &ANASTASIS_back_generic_decrement_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, + "pay", + &pay_challenge + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING, + "back", + &ANASTASIS_back_generic_decrement_ + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, + "solve_challenge", + &solve_challenge + }, + { + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING, + "back", + &back_challenge_solving + }, + { ANASTASIS_RECOVERY_STATE_INVALID, NULL, NULL } + }; + const char *s = json_string_value (json_object_get (state, + "recovery_state")); + enum ANASTASIS_RecoveryState rs; + + GNUNET_assert (NULL != s); + rs = ANASTASIS_recovery_state_from_string_ (s); + if (ANASTASIS_RECOVERY_STATE_INVALID == rs) + { + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, + "'recovery_state' field invalid"); + return NULL; + } + for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) + { + if ( (rs == dispatchers[i].recovery_state) && + (0 == strcmp (action, + dispatchers[i].recovery_action)) ) + { + return dispatchers[i].fun (state, + arguments, + cb, + cb_cls); + } + } + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID, + action); + return NULL; } @@ -2806,11 +2631,8 @@ ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state, ANASTASIS_ActionCallback cb, void *cb_cls) { - json_t *version; - json_t *providers; - const json_t *attributes; - struct RecoverSecretState *rss; - const char *provider_url; + const json_t *providers; + json_t *attributes; providers = json_object_get (state, "authentication_providers"); @@ -2836,46 +2658,13 @@ ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state, "'identity_attributes' missing"); return NULL; } - rss = GNUNET_new (struct RecoverSecretState); - rss->id_data = json_incref ((json_t *) attributes); - version = json_object_get (arguments, - "version"); - if (NULL != version) - { - rss->version = (unsigned int) json_integer_value (version); - rss->have_version = true; - } - rss->state = json_incref (state); - rss->cb = cb; - rss->cb_cls = cb_cls; - rss->pending = 1; /* decremented after initialization loop */ - - provider_url = json_string_value (json_object_get (arguments, - "provider_url")); - if (NULL != provider_url) - { - rss->provider_url = GNUNET_strdup (provider_url); - begin_query_provider (rss, - provider_url); - } - else - { - json_t *prov; - const char *url; - - json_object_foreach (providers, url, prov) { - begin_query_provider (rss, - url); - } - } - rss->pending--; - if (0 == rss->pending) - { - providers_complete (rss); - if (NULL == rss->cb) - return NULL; - } - rss->ra.cleanup = &free_rss; - rss->ra.cleanup_cls = rss; - return &rss->ra; + json_object_set (state, + "identity_attributes", + attributes); + set_state (state, + ANASTASIS_RECOVERY_STATE_SECRET_SELECTING); + cb (cb_cls, + TALER_EC_NONE, + state); + return NULL; } diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c index f55eece..4b5ad7b 100644 --- a/src/reducer/anastasis_api_redux.c +++ b/src/reducer/anastasis_api_redux.c @@ -1,16 +1,16 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -36,6 +36,12 @@ */ #define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES +/** + * How long do we wait in a more "synchronous" + * scenaro for a /config reply from an Anastasis provider. + */ +#define CONFIG_FAST_TIMEOUT GNUNET_TIME_UNIT_SECONDS + #define GENERATE_STRING(STRING) #STRING, static const char *generic_strings[] = { @@ -132,6 +138,21 @@ struct ConfigRequest struct ConfigReduxWaiting *w_tail; /** + * When did we start? + */ + struct GNUNET_TIME_Absolute start_time; + + /** + * When do we time out? + */ + struct GNUNET_TIME_Absolute timeout_at; + + /** + * How long do we wait before trying again? + */ + struct GNUNET_TIME_Relative backoff; + + /** * Obtained status code. */ unsigned int http_status; @@ -152,11 +173,6 @@ struct ConfigRequest char *business_name; /** - * currency used by the anastasis backend. - */ - char *currency; - - /** * Array of authorization methods supported by the server. */ struct AuthorizationMethodConfig *methods; @@ -188,9 +204,9 @@ struct ConfigRequest struct TALER_Amount liability_limit; /** - * Server salt. + * Provider salt. */ - struct ANASTASIS_CRYPTO_ProviderSaltP salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; /** * Task to timeout /config requests. @@ -234,6 +250,26 @@ static json_t *redux_countries; */ static json_t *provider_list; +/** + * External reducer binary or NULL + * to use internal reducer. + */ +static char *external_reducer_binary; + + +const char * +ANASTASIS_REDUX_probe_external_reducer (void) +{ + if (NULL != external_reducer_binary) + return external_reducer_binary; + external_reducer_binary = getenv ("ANASTASIS_EXTERNAL_REDUCER"); + if (NULL != external_reducer_binary) + unsetenv ("ANASTASIS_EXTERNAL_REDUCER"); + + return external_reducer_binary; + +} + /** * Extract the mode of a state from json @@ -262,7 +298,7 @@ ANASTASIS_generic_state_from_string_ (const char *state_string) if (0 == strcmp (state_string, generic_strings[i])) return i; - return ANASTASIS_GENERIC_STATE_ERROR; + return ANASTASIS_GENERIC_STATE_INVALID; } @@ -291,6 +327,8 @@ ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb, GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("detail", detail)), + GNUNET_JSON_pack_string ("reducer_type", + "error"), GNUNET_JSON_pack_uint64 ("code", ec), GNUNET_JSON_pack_string ("hint", @@ -344,7 +382,6 @@ free_config_request (struct ConfigRequest *cr) ANASTASIS_config_cancel (cr->co); if (NULL != cr->tt) GNUNET_SCHEDULER_cancel (cr->tt); - GNUNET_free (cr->currency); GNUNET_free (cr->url); GNUNET_free (cr->business_name); for (unsigned int i = 0; i<cr->methods_length; i++) @@ -471,13 +508,11 @@ notify_waiting (struct ConfigRequest *cr) "authentication_providers", provider_list = json_object ())); } - provider_list = json_object_get (w->state, - "authentication_providers"); - GNUNET_assert (NULL != provider_list); - if (TALER_EC_NONE != cr->ec) { prov = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "error"), GNUNET_JSON_pack_uint64 ("error_code", cr->ec), GNUNET_JSON_pack_uint64 ("http_status", @@ -503,6 +538,8 @@ notify_waiting (struct ConfigRequest *cr) mj)); } prov = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "ok"), GNUNET_JSON_pack_array_steal ("methods", methods_list), TALER_JSON_pack_amount ("annual_fee", @@ -511,14 +548,12 @@ notify_waiting (struct ConfigRequest *cr) &cr->truth_upload_fee), TALER_JSON_pack_amount ("liability_limit", &cr->liability_limit), - GNUNET_JSON_pack_string ("currency", - cr->currency), GNUNET_JSON_pack_string ("business_name", cr->business_name), GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes", cr->storage_limit_in_megabytes), - GNUNET_JSON_pack_data_auto ("salt", - &cr->salt), + GNUNET_JSON_pack_data_auto ("provider_salt", + &cr->provider_salt), GNUNET_JSON_pack_uint64 ("http_status", cr->http_status)); } @@ -531,68 +566,108 @@ notify_waiting (struct ConfigRequest *cr) w->state); abort_provider_config_cb (w); } +} + + +/** + * Notify anyone waiting on @a cr that the request is done + * (successful or failed). + * + * @param[in,out] cls request that completed + */ +static void +notify_waiting_cb (void *cls) +{ + struct ConfigRequest *cr = cls; + cr->tt = NULL; + notify_waiting (cr); } /** + * Function called when it is time to retry a + * failed /config request. + * + * @param cls the `struct ConfigRequest *` to retry. + */ +static void +retry_config (void *cls); + + +/** * Function called with the results of a #ANASTASIS_get_config(). * * @param cls closure - * @param http_status HTTP status of the request * @param acfg anastasis configuration */ static void config_cb (void *cls, - unsigned int http_status, const struct ANASTASIS_Config *acfg) { struct ConfigRequest *cr = cls; cr->co = NULL; - GNUNET_SCHEDULER_cancel (cr->tt); - cr->tt = NULL; - cr->http_status = http_status; - if (MHD_HTTP_OK != http_status) - cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED; - if ( (MHD_HTTP_OK == http_status) && + if (NULL != cr->tt) + { + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = NULL; + } + cr->http_status = acfg->http_status; + if (MHD_HTTP_OK != acfg->http_status) + { + if (0 == acfg->http_status) + cr->ec = TALER_EC_ANASTASIS_GENERIC_PROVIDER_UNREACHABLE; + else + cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED; + } + if ( (MHD_HTTP_OK == acfg->http_status) && (NULL == acfg) ) { cr->http_status = MHD_HTTP_NOT_FOUND; cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED; } - else if (NULL != acfg) + else { - if (0 == acfg->storage_limit_in_megabytes) + if (0 == acfg->details.ok.storage_limit_in_megabytes) { cr->http_status = 0; cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG; } else { - GNUNET_free (cr->currency); - cr->currency = GNUNET_strdup (acfg->currency); + cr->ec = TALER_EC_NONE; GNUNET_free (cr->business_name); - cr->business_name = GNUNET_strdup (acfg->business_name); + cr->business_name = GNUNET_strdup (acfg->details.ok.business_name); for (unsigned int i = 0; i<cr->methods_length; i++) GNUNET_free (cr->methods[i].type); GNUNET_free (cr->methods); - cr->methods = GNUNET_new_array (acfg->methods_length, + cr->methods = GNUNET_new_array (acfg->details.ok.methods_length, struct AuthorizationMethodConfig); - for (unsigned int i = 0; i<acfg->methods_length; i++) + for (unsigned int i = 0; i<acfg->details.ok.methods_length; i++) { - cr->methods[i].type = GNUNET_strdup (acfg->methods[i].type); - cr->methods[i].usage_fee = acfg->methods[i].usage_fee; + cr->methods[i].type = GNUNET_strdup (acfg->details.ok.methods[i].type); + cr->methods[i].usage_fee = acfg->details.ok.methods[i].usage_fee; } - cr->methods_length = acfg->methods_length; - cr->storage_limit_in_megabytes = acfg->storage_limit_in_megabytes; - cr->annual_fee = acfg->annual_fee; - cr->truth_upload_fee = acfg->truth_upload_fee; - cr->liability_limit = acfg->liability_limit; - cr->salt = acfg->salt; + cr->methods_length = acfg->details.ok.methods_length; + cr->storage_limit_in_megabytes = + acfg->details.ok.storage_limit_in_megabytes; + cr->annual_fee = acfg->details.ok.annual_fee; + cr->truth_upload_fee = acfg->details.ok.truth_upload_fee; + cr->liability_limit = acfg->details.ok.liability_limit; + cr->provider_salt = acfg->details.ok.provider_salt; } } notify_waiting (cr); + if (MHD_HTTP_OK != acfg->http_status) + { + cr->backoff = GNUNET_TIME_STD_BACKOFF (cr->backoff); + GNUNET_assert (NULL == cr->tt); + GNUNET_assert (NULL != cr->url); + cr->tt = GNUNET_SCHEDULER_add_delayed (cr->backoff, + &retry_config, + cr); + } } @@ -607,22 +682,57 @@ config_request_timeout (void *cls) struct ConfigRequest *cr = cls; cr->tt = NULL; - ANASTASIS_config_cancel (cr->co); - cr->co = NULL; + if (NULL != cr->co) + { + ANASTASIS_config_cancel (cr->co); + cr->co = NULL; + } cr->http_status = 0; cr->ec = TALER_EC_GENERIC_TIMEOUT; notify_waiting (cr); + cr->backoff = GNUNET_TIME_STD_BACKOFF (cr->backoff); + GNUNET_assert (NULL == cr->tt); + GNUNET_assert (NULL != cr->url); + cr->tt = GNUNET_SCHEDULER_add_delayed (cr->backoff, + &retry_config, + cr); +} + + +static void +retry_config (void *cls) +{ + struct ConfigRequest *cr = cls; + + cr->tt = NULL; + if (NULL != cr->co) + { + ANASTASIS_config_cancel (cr->co); + cr->co = NULL; + } + cr->timeout_at = GNUNET_TIME_relative_to_absolute (CONFIG_GENERIC_TIMEOUT); + GNUNET_assert (NULL == cr->tt); + cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at, + &config_request_timeout, + cr); + cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_, + cr->url, + &config_cb, + cr); + GNUNET_break (NULL != cr->co); } /** * Schedule job to obtain Anastasis provider configuration at @a url. * + * @param timeout how long to wait for a reply * @param url base URL of Anastasis provider * @return check config handle */ static struct ConfigRequest * -check_config (const char *url) +check_config (struct GNUNET_TIME_Relative timeout, + const char *url) { struct ConfigRequest *cr; @@ -632,17 +742,47 @@ check_config (const char *url) cr->url)) continue; if (NULL != cr->co) + { + struct GNUNET_TIME_Relative duration; + struct GNUNET_TIME_Relative left; + struct GNUNET_TIME_Relative xleft; + + duration = GNUNET_TIME_absolute_get_duration (cr->start_time); + left = GNUNET_TIME_relative_subtract (timeout, + duration); + xleft = GNUNET_TIME_absolute_get_remaining (cr->timeout_at); + if (GNUNET_TIME_relative_cmp (left, + <, + xleft)) + { + /* new timeout is shorter! */ + cr->timeout_at = GNUNET_TIME_relative_to_absolute (left); + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at, + &config_request_timeout, + cr); + } return cr; /* already on it */ + } break; } if (NULL == cr) { cr = GNUNET_new (struct ConfigRequest); + cr->start_time = GNUNET_TIME_absolute_get (); cr->url = GNUNET_strdup (url); GNUNET_CONTAINER_DLL_insert (cr_head, cr_tail, cr); } + if (MHD_HTTP_OK == cr->http_status) + return cr; + cr->timeout_at = GNUNET_TIME_relative_to_absolute (timeout); + if (NULL != cr->tt) + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = GNUNET_SCHEDULER_add_at (cr->timeout_at, + &config_request_timeout, + cr); cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_, cr->url, &config_cb, @@ -652,12 +792,6 @@ check_config (const char *url) GNUNET_break (0); return NULL; } - else - { - cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT, - &config_request_timeout, - cr); - } return cr; } @@ -665,12 +799,12 @@ check_config (const char *url) /** * Begin asynchronous check for provider configurations. * - * @param currencies the currencies to initiate the provider checks for + * @param cc country code that was selected * @param[in,out] state to set provider list for * @return #TALER_EC_NONE on success */ static enum TALER_ErrorCode -begin_provider_config_check (const json_t *currencies, +begin_provider_config_check (const char *cc, json_t *state) { if (NULL == provider_list) @@ -718,14 +852,17 @@ begin_provider_config_check (const json_t *currencies, json_array_foreach (provider_arr, index, provider) { const char *url; - const char *cur; + const char *restricted = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("url", &url), - GNUNET_JSON_spec_string ("currency", - &cur), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("restricted", + &restricted), + NULL), GNUNET_JSON_spec_end () }; + json_t *prov; if (GNUNET_OK != GNUNET_JSON_parse (provider, @@ -736,33 +873,35 @@ begin_provider_config_check (const json_t *currencies, json_decref (pl); return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED; } - + if ( (NULL != restricted) && + (0 != strcmp (restricted, + cc)) ) { - bool found = false; - json_t *cu; - size_t off; - - json_array_foreach (currencies, off, cu) - { - const char *currency; - - currency = json_string_value (cu); - if (NULL == currency) - { - json_decref (pl); - return TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID; - } - found = (0 == strcasecmp (currency, - cur)); - } - if (! found) - continue; + /* skip */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Skipping provider restricted to country `%s'\n", + restricted); + continue; + } + if ( (NULL == restricted) && + (0 == strcmp (cc, + "xx")) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Running in demo mode, skipping unrestricted providers\n"); + /* demo mode, skipping regular providers */ + continue; } + prov = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "not-contacted")); + GNUNET_assert (NULL != prov); GNUNET_assert (0 == json_object_set_new (pl, url, - json_object ())); - check_config (url); + prov)); + check_config (CONFIG_GENERIC_TIMEOUT, + url); } GNUNET_assert (0 == json_object_set_new (state, @@ -990,7 +1129,6 @@ select_country (json_t *state, { const json_t *required_attrs; const json_t *country_code; - const json_t *currencies; const json_t *redux_id_attr; if (NULL == arguments) @@ -1040,23 +1178,11 @@ select_country (json_t *state, } } - currencies = json_object_get (arguments, - "currencies"); - if ( (NULL == currencies) || - (! json_is_array (currencies)) ) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_STATE_INVALID, - "'currencies' missing"); - return NULL; - } - /* We now have an idea of the currency, begin fetching - provider /configs (we likely need them later) */ + /* Begin fetching provider /configs (we likely need them later) */ { enum TALER_ErrorCode ec; - ec = begin_provider_config_check (currencies, + ec = begin_provider_config_check (json_string_value (country_code), state); if (TALER_EC_NONE != ec) { @@ -1096,10 +1222,6 @@ select_country (json_t *state, (json_t *) country_code)); GNUNET_assert (0 == json_object_set (state, - "currencies", - (json_t *) currencies)); - GNUNET_assert (0 == - json_object_set (state, "required_attributes", (json_t *) required_attrs)); cb (cb_cls, @@ -1143,7 +1265,8 @@ ANASTASIS_REDUX_add_provider_to_state_ (const char *url, struct ConfigRequest *cr; struct ConfigReduxWaiting *w; - cr = check_config (url); + cr = check_config (CONFIG_FAST_TIMEOUT, + url); w = GNUNET_new (struct ConfigReduxWaiting); w->cr = cr; w->state = json_incref (state); @@ -1156,8 +1279,10 @@ ANASTASIS_REDUX_add_provider_to_state_ (const char *url, w); if (NULL == cr->co) { - notify_waiting (cr); - return NULL; + if (NULL != cr->tt) + GNUNET_SCHEDULER_cancel (cr->tt); + cr->tt = GNUNET_SCHEDULER_add_now (¬ify_waiting_cb, + cr); } return &w->ra; } @@ -1228,19 +1353,22 @@ enter_user_attributes (json_t *state, const char *attribute_value; const char *regexp = NULL; const char *reglog = NULL; - int optional = false; + bool optional = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("validation-regex", - ®exp)), + ®exp), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("validation-logic", - ®log)), + ®log), + NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_boolean ("optional", - &optional)), + GNUNET_JSON_spec_bool ("optional", + &optional), + NULL), GNUNET_JSON_spec_end () }; @@ -1378,9 +1506,16 @@ ANASTASIS_add_provider_ (json_t *state, ANASTASIS_ActionCallback cb, void *cb_cls) { - json_t *urls; json_t *tlist; + if (NULL == arguments) + { + ANASTASIS_redux_fail_ (cb, + cb_cls, + TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, + "arguments missing"); + return true; /* cb was invoked */ + } tlist = json_object_get (state, "authentication_providers"); if (NULL == tlist) @@ -1392,47 +1527,19 @@ ANASTASIS_add_provider_ (json_t *state, "authentication_providers", tlist)); } - if (NULL == arguments) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "arguments missing"); - return true; - } - urls = json_object_get (arguments, - "urls"); - if (NULL == urls) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'urls' missing"); - return true; - } { - size_t index; - json_t *url; + json_t *params; + const char *url; - json_array_foreach (urls, index, url) + json_object_foreach (((json_t *) arguments), url, params) { - const char *url_str = json_string_value (url); - - if (NULL == url_str) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "'urls' must be strings"); - return true; - } GNUNET_assert (0 == - json_object_set_new (tlist, - url_str, - json_object ())); + json_object_set (tlist, + url, + params)); } } - return false; + return false; /* cb not invoked */ } @@ -1502,6 +1609,256 @@ typedef struct ANASTASIS_ReduxAction * void *cb_cls); +/** + * Closure for read operations on the external reducer. + */ +struct ExternalReducerCls +{ + struct GNUNET_Buffer read_buffer; + struct GNUNET_SCHEDULER_Task *read_task; + struct GNUNET_DISK_PipeHandle *reducer_stdin; + struct GNUNET_DISK_PipeHandle *reducer_stdout; + struct GNUNET_OS_Process *reducer_process; + ANASTASIS_ActionCallback action_cb; + void *action_cb_cls; +}; + +/** + * Clean up and destroy the external reducer state. + * + * @param cls closure, a 'struct ExternalReducerCls *' + */ +static void +cleanup_external_reducer (void *cls) +{ + struct ExternalReducerCls *red_cls = cls; + + if (NULL != red_cls->read_task) + { + GNUNET_SCHEDULER_cancel (red_cls->read_task); + red_cls->read_task = NULL; + } + + GNUNET_buffer_clear (&red_cls->read_buffer); + if (NULL != red_cls->reducer_stdin) + { + GNUNET_DISK_pipe_close (red_cls->reducer_stdin); + red_cls->reducer_stdin = NULL; + } + if (NULL != red_cls->reducer_stdout) + { + GNUNET_DISK_pipe_close (red_cls->reducer_stdout); + red_cls->reducer_stdout = NULL; + } + + if (NULL != red_cls->reducer_process) + { + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + enum GNUNET_GenericReturnValue pwret; + + pwret = GNUNET_OS_process_wait_status (red_cls->reducer_process, + &type, + &code); + + GNUNET_assert (GNUNET_SYSERR != pwret); + if (GNUNET_NO == pwret) + { + GNUNET_OS_process_kill (red_cls->reducer_process, + SIGTERM); + GNUNET_assert (GNUNET_SYSERR != GNUNET_OS_process_wait ( + red_cls->reducer_process)); + } + + GNUNET_OS_process_destroy (red_cls->reducer_process); + red_cls->reducer_process = NULL; + } + + GNUNET_free (red_cls); +} + + +/** + * Task called when + * + * @param cls closure, a 'struct ExternalReducerCls *' + */ +static void +external_reducer_read_cb (void *cls) +{ + struct ExternalReducerCls *red_cls = cls; + ssize_t sret; + char buf[256]; + + red_cls->read_task = NULL; + + sret = GNUNET_DISK_file_read (GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_READ), + buf, + 256); + if (sret < 0) + { + GNUNET_break (0); + red_cls->action_cb (red_cls->action_cb_cls, + TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, + NULL); + cleanup_external_reducer (red_cls); + return; + } + else if (0 == sret) + { + char *str = GNUNET_buffer_reap_str (&red_cls->read_buffer); + json_t *json; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got external reducer response: '%s'\n", + str); + + json = json_loads (str, 0, NULL); + + if (NULL == json) + { + GNUNET_break (0); + red_cls->action_cb (red_cls->action_cb_cls, + TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR, + NULL); + cleanup_external_reducer (red_cls); + return; + } + + { + enum TALER_ErrorCode ec; + ec = json_integer_value (json_object_get (json, "code")); + + red_cls->action_cb (red_cls->action_cb_cls, + ec, + json); + } + cleanup_external_reducer (red_cls); + return; + } + else + { + GNUNET_buffer_write (&red_cls->read_buffer, + buf, + sret); + + red_cls->read_task = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_READ), + external_reducer_read_cb, + red_cls); + } +} + + +/** + * Handle an action using an external reducer, i.e. + * by shelling out to another process. + */ +static struct ANASTASIS_ReduxAction * +redux_action_external (const char *ext_reducer, + const json_t *state, + const char *action, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls) +{ + char *arg_str; + char *state_str = json_dumps (state, JSON_COMPACT); + ssize_t sret; + struct ExternalReducerCls *red_cls = GNUNET_new (struct ExternalReducerCls); + + if (NULL == arguments) + arg_str = GNUNET_strdup ("{}"); + else + arg_str = json_dumps (arguments, JSON_COMPACT); + + red_cls->action_cb = cb; + red_cls->action_cb_cls = cb_cls; + + GNUNET_assert (NULL != (red_cls->reducer_stdin = GNUNET_DISK_pipe ( + GNUNET_DISK_PF_NONE))); + GNUNET_assert (NULL != (red_cls->reducer_stdout = GNUNET_DISK_pipe ( + GNUNET_DISK_PF_NONE))); + + /* By the time we're here, this variable should be unset, because + otherwise using anastasis-reducer as the external reducer + will lead to infinite recursion. */ + GNUNET_assert (NULL == getenv ("ANASTASIS_EXTERNAL_REDUCER")); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting external reducer with action '%s' and argument '%s'\n", + action, + arg_str); + + red_cls->reducer_process = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + red_cls->reducer_stdin, + red_cls->reducer_stdout, + NULL, + ext_reducer, + ext_reducer, + "-a", + arg_str, + action, + NULL); + + GNUNET_free (arg_str); + + if (NULL == red_cls->reducer_process) + { + GNUNET_break (0); + GNUNET_free (state_str); + cleanup_external_reducer (red_cls); + return NULL; + } + + /* Close pipe ends we don't use. */ + GNUNET_assert (GNUNET_OK == + GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin, + GNUNET_DISK_PIPE_END_READ)); + GNUNET_assert (GNUNET_OK == + GNUNET_DISK_pipe_close_end (red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_WRITE)); + + sret = GNUNET_DISK_file_write_blocking (GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdin, + GNUNET_DISK_PIPE_END_WRITE), + state_str, + strlen (state_str)); + GNUNET_free (state_str); + if (sret <= 0) + { + GNUNET_break (0); + cleanup_external_reducer (red_cls); + return NULL; + } + + GNUNET_assert (GNUNET_OK == + GNUNET_DISK_pipe_close_end (red_cls->reducer_stdin, + GNUNET_DISK_PIPE_END_WRITE)); + + red_cls->read_task = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle ( + red_cls->reducer_stdout, + GNUNET_DISK_PIPE_END_READ), + external_reducer_read_cb, + red_cls); + + { + struct ANASTASIS_ReduxAction *ra = GNUNET_new (struct + ANASTASIS_ReduxAction); + ra->cleanup_cls = red_cls; + ra->cleanup = cleanup_external_reducer; + return ra; + } +} + + struct ANASTASIS_ReduxAction * ANASTASIS_redux_action (const json_t *state, const char *action, @@ -1520,6 +1877,7 @@ ANASTASIS_redux_action (const json_t *state, "select_continent", &select_continent }, + /* Deprecated alias for "back" from that state, should be removed eventually. */ { ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, "unselect_continent", @@ -1527,6 +1885,11 @@ ANASTASIS_redux_action (const json_t *state, }, { ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, + "back", + &unselect_continent + }, + { + ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, "select_country", &select_country }, @@ -1536,11 +1899,6 @@ ANASTASIS_redux_action (const json_t *state, &select_continent }, { - ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING, - "unselect_continent", - &unselect_continent - }, - { ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING, "enter_user_attributes", &enter_user_attributes @@ -1555,13 +1913,25 @@ ANASTASIS_redux_action (const json_t *state, "back", &ANASTASIS_back_generic_decrement_ }, - { ANASTASIS_GENERIC_STATE_ERROR, NULL, NULL } + { ANASTASIS_GENERIC_STATE_INVALID, NULL, NULL } }; bool recovery_mode = false; const char *s = json_string_value (json_object_get (state, "backup_state")); enum ANASTASIS_GenericState gs; + /* If requested, handle action with external reducer, used for testing. */ + { + const char *ext_reducer = ANASTASIS_REDUX_probe_external_reducer (); + if (NULL != ext_reducer) + return redux_action_external (ext_reducer, + state, + action, + arguments, + cb, + cb_cls); + } + if (NULL == s) { s = json_string_value (json_object_get (state, @@ -1583,7 +1953,7 @@ ANASTASIS_redux_action (const json_t *state, new_state = json_deep_copy (state); GNUNET_assert (NULL != new_state); - if (gs != ANASTASIS_GENERIC_STATE_ERROR) + if (gs != ANASTASIS_GENERIC_STATE_INVALID) { for (unsigned int i = 0; NULL != dispatchers[i].fun; i++) { @@ -1737,3 +2107,68 @@ ANASTASIS_REDUX_load_continents_ () GNUNET_JSON_pack_array_steal ("continents", continents)); } + + +/** + * Lookup @a provider_salt of @a provider_url in @a state. + * + * @param state the state to inspect + * @param provider_url provider to look into + * @param[out] provider_salt value to extract + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +ANASTASIS_reducer_lookup_salt ( + const json_t *state, + const char *provider_url, + struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt) +{ + const json_t *aps; + const json_t *cfg; + uint32_t http_status = 0; + const char *status; + bool no_salt; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("status", + &status), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("http_status", + &http_status), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("provider_salt", + provider_salt), + &no_salt), + GNUNET_JSON_spec_end () + }; + + aps = json_object_get (state, + "authentication_providers"); + if (NULL == aps) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + cfg = json_object_get (aps, + provider_url); + if (NULL == cfg) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (cfg, + spec, + NULL, NULL)) + { + /* provider not working */ + GNUNET_break_op (0); + return GNUNET_NO; + } + if (0 == strcmp (status, + "disabled")) + return GNUNET_NO; + if (no_salt) + return GNUNET_NO; + return GNUNET_OK; +} diff --git a/src/reducer/anastasis_api_redux.h b/src/reducer/anastasis_api_redux.h index 4d62d5e..03eef33 100644 --- a/src/reducer/anastasis_api_redux.h +++ b/src/reducer/anastasis_api_redux.h @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -25,7 +25,7 @@ #define ANASTASIS_GENERIC_STATES(REDUX_STATE) \ - REDUX_STATE (ERROR) \ + REDUX_STATE (INVALID) \ REDUX_STATE (CONTINENT_SELECTING) \ REDUX_STATE (COUNTRY_SELECTING) \ REDUX_STATE (USER_ATTRIBUTES_COLLECTING) @@ -108,6 +108,42 @@ ANASTASIS_REDUX_load_continents_ (void); /** + * Try to obtain configuration information on all configured + * providers. Upon success, call @a cb with the updated provider + * status data. + * + * @param[in] state we are in + * @param arguments our arguments with the solution + * @param cb functiont o call with the new state + * @param cb_cls closure for @a cb + * @return handle to cancel challenge selection step + */ +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_poll_providers_ (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls); + + +/** + * Check if we have information on all providers involved in + * a recovery procedure, and if not, try to obtain it. Upon + * success, call @a cb with the updated provider status data. + * + * @param[in] state we are in + * @param arguments our arguments with the solution + * @param cb functiont o call with the new state + * @param cb_cls closure for @a cb + * @return handle to cancel challenge selection step + */ +struct ANASTASIS_ReduxAction * +ANASTASIS_REDUX_sync_providers_ (json_t *state, + const json_t *arguments, + ANASTASIS_ActionCallback cb, + void *cb_cls); + + +/** * Returns the enum value to a string value of a state. * * @param state_string string to convert @@ -168,6 +204,20 @@ ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs); /** + * Lookup @a salt of @a provider_url in @a state. + * + * @param state the state to inspect + * @param provider_url provider to look into + * @param[out] salt value to extract + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +ANASTASIS_reducer_lookup_salt (const json_t *state, + const char *provider_url, + struct ANASTASIS_CRYPTO_ProviderSaltP *salt); + + +/** * Function to return a json error response. * * @param cb callback to give error to @@ -328,6 +378,16 @@ ANASTASIS_backup_action_ (json_t *state, /** + * Check if an external reducer binary is requested. + * Cache the result and unset the corresponding environment + * variable. + * + * @returns name of the external reducer or NULL to user internal reducer + */ +const char * +ANASTASIS_REDUX_probe_external_reducer (void); + +/** * Generic container for an action with asynchronous activities. */ struct ANASTASIS_ReduxAction diff --git a/src/reducer/validation_CH_AHV.c b/src/reducer/validation_CH_AHV.c index 6beded1..4ea973c 100644 --- a/src/reducer/validation_CH_AHV.c +++ b/src/reducer/validation_CH_AHV.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_CZ_BN.c b/src/reducer/validation_CZ_BN.c index 85dea4a..b570841 100644 --- a/src/reducer/validation_CZ_BN.c +++ b/src/reducer/validation_CZ_BN.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_DE_SVN.c b/src/reducer/validation_DE_SVN.c index 7096708..e753f0c 100644 --- a/src/reducer/validation_DE_SVN.c +++ b/src/reducer/validation_DE_SVN.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_DE_TIN.c b/src/reducer/validation_DE_TIN.c index 0412f87..5678579 100644 --- a/src/reducer/validation_DE_TIN.c +++ b/src/reducer/validation_DE_TIN.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_ES_DNI.c b/src/reducer/validation_ES_DNI.c new file mode 100644 index 0000000..5fb3885 --- /dev/null +++ b/src/reducer/validation_ES_DNI.c @@ -0,0 +1,184 @@ +/* + This file is part of Anastasis + Copyright (C) 2021 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_ES_DNI.c + * @brief validation logic for Spanish Documento Nacional de Identidad numbers, and Número de Identificación de Extranjeros + * @author Christian Grothoff + * + * Examples: + * 12345678Z, 39740191D, 14741806W, X8095495R + */ +#include <string.h> +#include <stdbool.h> +#include <stdio.h> + +/** + * Function to validate a Spanish CIF number. + * + * @param civ number to validate + * @return true if validation passed, else false + */ +static bool +validate_cif (const char *cif) +{ + size_t slen = strlen (cif); + char letter = cif[0]; + const char *number = &cif[1]; + char control = cif[slen - 1]; + unsigned int sum = 0; + + if (9 != slen) + return false; + + for (unsigned int i = 0; i < slen - 2; i++) + { + unsigned int n = number[i] - '0'; + + if (n >= 10) + return false; + if (0 == (i % 2)) + { + n *= 2; + sum += n < 10 ? n : n - 9; + } + else + { + sum += n; + } + } + sum %= 10; + if (0 != sum) + sum = 10 - sum; + { + char control_digit = "0123456789"[sum]; + char control_letter = "JABCDEFGHI"[sum]; + + switch (letter) + { + case 'A': + case 'B': + case 'E': + case 'H': + return control == control_digit; + case 'N': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'W': + return control == control_letter; + default: + return (control == control_letter) || + (control == control_digit); + } + } +} + + +/** + * Function to validate a Spanish DNI number. + * + * See https://www.ordenacionjuego.es/en/calculo-digito-control + * + * @param dni_number number to validate (input) + * @return true if validation passed, else false + */ +bool +ES_DNI_check (const char *dni_number) +{ + const char map[] = "TRWAGMYFPDXBNJZSQVHLCKE"; + unsigned int num; + char chksum; + unsigned int fact; + char dummy; + + if (strlen (dni_number) < 8) + return false; + switch (dni_number[0]) + { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + /* CIF: [A-W]\d{7}[0-9A-J] */ + /* CIF is for companies, we only take those + of individuals here! */ + return false; /* CIV is not allowed! */ + case 'M': + /* special NIE, with CIF validation (?), + but for individuals, see + https://www.strongabogados.com/tax-id-spain.php */ + return validate_cif (dni_number); + case 'X': + case 'Y': + case 'Z': + /* NIE */ + fact = dni_number[0] - 'X'; + /* 7 or 8 digits */ + if (2 == sscanf (&dni_number[1], + "%8u%c%c", + &num, + &chksum, + &dummy)) + { + num += fact * 100000000; + } + else if (2 == sscanf (&dni_number[1], + "%7u%c%c", + &num, + &chksum, + &dummy)) + { + num += fact * 10000000; + } + else + { + return false; + } + break; + default: + fact = 0; + /* DNI */ + if (2 != sscanf (dni_number, + "%8u%c%c", + &num, + &chksum, + &dummy)) + return false; + break; + } + if (map[num % 23] != chksum) + return false; + return true; +} diff --git a/src/reducer/validation_FR_INSEE.c b/src/reducer/validation_FR_INSEE.c new file mode 100644 index 0000000..19d81fd --- /dev/null +++ b/src/reducer/validation_FR_INSEE.c @@ -0,0 +1,66 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_FR_INSEE.c + * @brief Validation for French INSEE Numbers + * @author Christian Grothoff + */ +#include <string.h> +#include <stdbool.h> +#include <stdio.h> + + +/** + * Function to validate a French INSEE number. + * + * See https://en.wikipedia.org/wiki/INSEE_code + * + * Note that we do not implement checks on the month + * and also allow non-binary prefixes. + * + * @param insee_number social security number to validate (input) + * @return true if validation passed, else false + */ +bool +FR_INSEE_check (const char *insee_number) +{ + char pfx[14]; + unsigned long long num; + unsigned int cc; + char dum; + + if (strlen (insee_number) != 15) + return false; + memcpy (pfx, + insee_number, + 13); + pfx[13] = '\0'; + if (1 != + sscanf (pfx, + "%llu%c", + &num, + &dum)) + return false; + if (1 != + sscanf (&insee_number[13], + "%u%c", + &cc, + &dum)) + return false; + if (97 - cc != num % 97) + return false; + return true; +} diff --git a/src/reducer/validation_IN_AADHAR.c b/src/reducer/validation_IN_AADHAR.c index 4c4901a..d53b655 100644 --- a/src/reducer/validation_IN_AADHAR.c +++ b/src/reducer/validation_IN_AADHAR.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_IT_CF.c b/src/reducer/validation_IT_CF.c index ca66a11..6e9c6c6 100644 --- a/src/reducer/validation_IT_CF.c +++ b/src/reducer/validation_IT_CF.c @@ -3,14 +3,14 @@ Copyright (C) 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_NL_BSN.c b/src/reducer/validation_NL_BSN.c new file mode 100644 index 0000000..f92bb38 --- /dev/null +++ b/src/reducer/validation_NL_BSN.c @@ -0,0 +1,57 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/validation_NL_BSN.c + * @brief Validation for Dutch Buergerservicenummern + * @author Christian Grothoff + */ +#include <string.h> +#include <stdbool.h> + + +/** + * Function to validate a Dutch Social Security number. + * + * See https://nl.wikipedia.org/wiki/Burgerservicenummer + * + * @param bsn_number social security number to validate (input) + * @return true if validation passed, else false + */ +bool +NL_BSN_check (const char *bsn_number) +{ + static const int factors[] = { + 9, 8, 7, 6, 5, 4, 3, 2, -1 + }; + unsigned int sum = 0; + + if (strlen (bsn_number) != 9) + return false; + for (unsigned int i = 0; i<8; i++) + { + unsigned char c = (unsigned char) bsn_number[i]; + + if ( ('0' > c) || ('9' < c) ) + return false; + sum += (c - '0') * factors[i]; + } + { + unsigned char c = (unsigned char) bsn_number[8]; + unsigned int v = (c - '0'); + + return (sum % 11 == v); + } +} diff --git a/src/reducer/validation_XX_SQUARE.c b/src/reducer/validation_XX_SQUARE.c index 88cf890..1f43400 100644 --- a/src/reducer/validation_XX_SQUARE.c +++ b/src/reducer/validation_XX_SQUARE.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/reducer/validation_XY_PRIME.c b/src/reducer/validation_XY_PRIME.c index 32bdce0..56aa724 100644 --- a/src/reducer/validation_XY_PRIME.c +++ b/src/reducer/validation_XY_PRIME.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** diff --git a/src/restclient/Makefile.am b/src/restclient/Makefile.am index 075d3a7..1a4d83c 100644 --- a/src/restclient/Makefile.am +++ b/src/restclient/Makefile.am @@ -17,9 +17,11 @@ libanastasisrest_la_LDFLAGS = \ libanastasisrest_la_SOURCES = \ anastasis_api_config.c \ anastasis_api_policy_store.c \ - anastasis_api_truth_store.c \ anastasis_api_policy_lookup.c \ - anastasis_api_keyshare_lookup.c \ + anastasis_api_policy_meta_lookup.c \ + anastasis_api_truth_challenge.c \ + anastasis_api_truth_solve.c \ + anastasis_api_truth_store.c \ anastasis_api_curl_defaults.c anastasis_api_curl_defaults.h libanastasisrest_la_LIBADD = \ -lgnunetcurl \ @@ -27,9 +29,10 @@ libanastasisrest_la_LIBADD = \ -lgnunetutil \ -ljansson \ -ltalerjson \ - -ltalerutil \ + -ltalercurl \ -ltalermerchant \ -ltalerjson \ + -ltalerutil \ $(XLIB) if HAVE_LIBCURL @@ -39,4 +42,3 @@ if HAVE_LIBGNURL libanastasisrest_la_LIBADD += -lgnurl endif endif - diff --git a/src/restclient/anastasis_api_config.c b/src/restclient/anastasis_api_config.c index acb0967..aee0357 100644 --- a/src/restclient/anastasis_api_config.c +++ b/src/restclient/anastasis_api_config.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -86,6 +86,10 @@ handle_config_finished (void *cls, { struct ANASTASIS_ConfigOperation *co = cls; const json_t *json = response; + struct ANASTASIS_Config acfg = { + .http_status = response_code, + .response = json + }; co->job = NULL; switch (response_code) @@ -99,29 +103,29 @@ handle_config_finished (void *cls, case MHD_HTTP_OK: { const char *name; - struct ANASTASIS_Config acfg; - json_t *methods; + const json_t *methods; + struct TALER_JSON_ProtocolVersion pv; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_string ("business_name", - &acfg.business_name), + &acfg.details.ok.business_name), GNUNET_JSON_spec_string ("version", - &acfg.version), - GNUNET_JSON_spec_string ("currency", - &acfg.currency), - GNUNET_JSON_spec_json ("methods", - &methods), + &acfg.details.ok.version), + TALER_JSON_spec_version ("version", + &pv), + GNUNET_JSON_spec_array_const ("methods", + &methods), GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", - &acfg.storage_limit_in_megabytes), + &acfg.details.ok.storage_limit_in_megabytes), TALER_JSON_spec_amount_any ("annual_fee", - &acfg.annual_fee), + &acfg.details.ok.annual_fee), TALER_JSON_spec_amount_any ("truth_upload_fee", - &acfg.truth_upload_fee), + &acfg.details.ok.truth_upload_fee), TALER_JSON_spec_amount_any ("liability_limit", - &acfg.liability_limit), - GNUNET_JSON_spec_fixed_auto ("server_salt", - &acfg.salt), + &acfg.details.ok.liability_limit), + GNUNET_JSON_spec_fixed_auto ("provider_salt", + &acfg.details.ok.provider_salt), GNUNET_JSON_spec_end () }; @@ -131,80 +135,54 @@ handle_config_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - response_code = 0; + json_dumpf (json, + stderr, + JSON_INDENT (2)); + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } if (0 != strcmp (name, "anastasis")) { GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } + if ( (ANASTASIS_PROTOCOL_CURRENT < pv.current) && + (ANASTASIS_PROTOCOL_CURRENT < pv.current - pv.age) ) { - unsigned int age; - unsigned int revision; - unsigned int current; - char dummy; - - if (3 != sscanf (acfg.version, - "%u:%u:%u%c", - ¤t, - &revision, - &age, - &dummy)) - { - GNUNET_break_op (0); - response_code = 0; - GNUNET_JSON_parse_free (spec); - break; - } - if ( (ANASTASIS_PROTOCOL_CURRENT < current) && - (ANASTASIS_PROTOCOL_CURRENT < current - age) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Provider protocol version too new\n"); - response_code = 0; - GNUNET_JSON_parse_free (spec); - break; - } - if ( (ANASTASIS_PROTOCOL_CURRENT > current) && - (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > current) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Provider protocol version too old\n"); - GNUNET_break_op (0); - response_code = 0; - GNUNET_JSON_parse_free (spec); - break; - } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider protocol version too new\n"); + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_VERSION_MALFORMED; + break; } - if ( (GNUNET_OK != - TALER_amount_cmp_currency (&acfg.liability_limit, - &acfg.annual_fee)) || - (0 != - strcasecmp (acfg.currency, - acfg.annual_fee.currency)) ) + if ( (ANASTASIS_PROTOCOL_CURRENT > pv.current) && + (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > pv.current) ) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider protocol version too old\n"); GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_VERSION_MALFORMED; break; } - - if (! json_is_array (methods)) + acfg.details.ok.methods_length = (unsigned int) json_array_size (methods); + if (((size_t) acfg.details.ok.methods_length) != + json_array_size (methods)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - acfg.methods_length = json_array_size (methods); { - struct ANASTASIS_AuthorizationMethodConfig mcfg[GNUNET_NZL ( - acfg.methods_length)]; + struct ANASTASIS_AuthorizationMethodConfig mcfg[ + GNUNET_NZL (acfg.details.ok.methods_length)]; - for (unsigned int i = 0; i<acfg.methods_length; i++) + for (unsigned int i = 0; i<acfg.details.ok.methods_length; i++) { struct ANASTASIS_AuthorizationMethodConfig *m = &mcfg[i]; struct GNUNET_JSON_Specification spec[] = { @@ -222,16 +200,17 @@ handle_config_finished (void *cls, NULL, NULL)) ) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; goto end; } } - acfg.methods = mcfg; + acfg.details.ok.methods = mcfg; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Good backend found at `%s'\n", + co->url); co->cb (co->cb_cls, - MHD_HTTP_OK, &acfg); - GNUNET_JSON_parse_free (spec); ANASTASIS_config_cancel (co); return; } @@ -257,8 +236,7 @@ handle_config_finished (void *cls, } end: co->cb (co->cb_cls, - response_code, - NULL); + &acfg); ANASTASIS_config_cancel (co); } diff --git a/src/restclient/anastasis_api_curl_defaults.c b/src/restclient/anastasis_api_curl_defaults.c index b777bae..f64347b 100644 --- a/src/restclient/anastasis_api_curl_defaults.c +++ b/src/restclient/anastasis_api_curl_defaults.c @@ -3,14 +3,14 @@ Copyright (C) 2014-2019 Anastasis SARL Anastasis 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 + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ @@ -20,6 +20,7 @@ * @author Florian Dold */ #include "platform.h" +#include <taler/taler_curl_lib.h> #include "anastasis_api_curl_defaults.h" CURL * @@ -34,13 +35,17 @@ ANASTASIS_curl_easy_get_ (const char *url) curl_easy_setopt (eh, CURLOPT_URL, url)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_FOLLOWLOCATION, - 1L)); + TALER_curl_set_secure_redirect_policy (eh, + url); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_TCP_FASTOPEN, 1L)); + /* Enable compression (using whatever curl likes), see + https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */ + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ACCEPT_ENCODING, + "")); return eh; } diff --git a/src/restclient/anastasis_api_curl_defaults.h b/src/restclient/anastasis_api_curl_defaults.h index 4d990af..948b931 100644 --- a/src/restclient/anastasis_api_curl_defaults.h +++ b/src/restclient/anastasis_api_curl_defaults.h @@ -3,14 +3,14 @@ Copyright (C) 2014-2019 Anastasis SARL Anastasis 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 + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ diff --git a/src/restclient/anastasis_api_keyshare_lookup.c b/src/restclient/anastasis_api_keyshare_lookup.c index 50e0d67..4840a7e 100644 --- a/src/restclient/anastasis_api_keyshare_lookup.c +++ b/src/restclient/anastasis_api_keyshare_lookup.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -228,7 +228,6 @@ handle_keyshare_lookup_finished (void *cls, kdd.status = ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION; kdd.details.redirect_url = kslo->location; break; - case MHD_HTTP_ALREADY_REPORTED: case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, authentication required/failed */ kdd.status = ANASTASIS_KSD_INVALID_ANSWER; @@ -245,19 +244,57 @@ handle_keyshare_lookup_finished (void *cls, /* Nothing really to verify */ kdd.status = ANASTASIS_KSD_AUTHENTICATION_TIMEOUT; break; - case MHD_HTTP_GONE: - /* Nothing really to verify */ - kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; - break; - case MHD_HTTP_EXPECTATION_FAILED: + case MHD_HTTP_CONFLICT: /* Nothing really to verify */ kdd.status = ANASTASIS_KSD_CLIENT_FAILURE; - kdd.details.server_failure.http_status = MHD_HTTP_EXPECTATION_FAILED; + kdd.details.server_failure.http_status = MHD_HTTP_CONFLICT; kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, data_size); break; + case MHD_HTTP_GONE: + /* Nothing really to verify */ + kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; + break; case MHD_HTTP_TOO_MANY_REQUESTS: kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ( + "request_limit", + &kdd.details.rate_limit_exceeded.request_limit), + GNUNET_JSON_spec_relative_time ( + "request_frequency", + &kdd.details.rate_limit_exceeded.request_frequency), + GNUNET_JSON_spec_end () + }; + json_t *reply; + + reply = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == reply) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kdd.details.server_failure.http_status = response_code; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply); + kdd.details.server_failure.http_status = response_code; + json_decref (reply); + break; + } + json_decref (reply); + } break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API @@ -411,7 +448,7 @@ ANASTASIS_keyshare_lookup ( char *val; char *hdr; - /* Set Truth-Decryption-Key header */ + /* Set Anastasis-Truth-Decryption-Key header */ val = GNUNET_STRINGS_data_to_string_alloc (truth_key, sizeof (*truth_key)); GNUNET_asprintf (&hdr, @@ -487,8 +524,8 @@ ANASTASIS_keyshare_lookup ( answer_s, "timeout_ms", (0 != timeout.rel_value_us) - ? timeout_ms - : NULL, + ? timeout_ms + : NULL, NULL); GNUNET_free (answer_s); } @@ -500,8 +537,8 @@ ANASTASIS_keyshare_lookup ( path, "timeout_ms", (0 != timeout.rel_value_us) - ? timeout_ms - : NULL, + ? timeout_ms + : NULL, NULL); } } diff --git a/src/restclient/anastasis_api_policy_lookup.c b/src/restclient/anastasis_api_policy_lookup.c index e21ed58..b3132ef 100644 --- a/src/restclient/anastasis_api_policy_lookup.c +++ b/src/restclient/anastasis_api_policy_lookup.c @@ -3,16 +3,16 @@ Copyright (C) 2014-2019 Anastasis SARL ANASTASIS is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as + it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. ANASTASIS 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 Lesser General Public License for more details. + GNU General Public License for more details. - You should have received a copy of the GNU Lesser General Public + You should have received a copy of the GNU General Public License along with ANASTASIS; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> */ @@ -106,6 +106,9 @@ handle_policy_lookup_finished (void *cls, size_t data_size) { struct ANASTASIS_PolicyLookupOperation *plo = cls; + struct ANASTASIS_DownloadDetails dd = { + .http_status = response_code + }; plo->job = NULL; switch (response_code) @@ -117,7 +120,6 @@ handle_policy_lookup_finished (void *cls, break; case MHD_HTTP_OK: { - struct ANASTASIS_DownloadDetails dd; struct ANASTASIS_UploadSignaturePS usp = { .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD), .purpose.size = htonl (sizeof (usp)), @@ -133,18 +135,17 @@ handle_policy_lookup_finished (void *cls, &plo->account_pub.pub)) { GNUNET_break_op (0); - response_code = 0; + dd.http_status = 0; + dd.ec = -1; // FIXME: needs new code in Gana! break; } /* Success, call callback with all details! */ - memset (&dd, 0, sizeof (dd)); - dd.sig = plo->account_sig; - dd.curr_policy_hash = usp.new_recovery_data_hash; - dd.policy = data; - dd.policy_size = data_size; - dd.version = plo->version; + dd.details.ok.sig = plo->account_sig; + dd.details.ok.curr_policy_hash = usp.new_recovery_data_hash; + dd.details.ok.policy = data; + dd.details.ok.policy_size = data_size; + dd.details.ok.version = plo->version; plo->cb (plo->cb_cls, - response_code, &dd); plo->cb = NULL; ANASTASIS_policy_lookup_cancel (plo); @@ -167,12 +168,10 @@ handle_policy_lookup_finished (void *cls, "Unexpected response code %u\n", (unsigned int) response_code); GNUNET_break (0); - response_code = 0; break; } plo->cb (plo->cb_cls, - response_code, - NULL); + &dd); plo->cb = NULL; ANASTASIS_policy_lookup_cancel (plo); } diff --git a/src/restclient/anastasis_api_policy_meta_lookup.c b/src/restclient/anastasis_api_policy_meta_lookup.c new file mode 100644 index 0000000..cf381fd --- /dev/null +++ b/src/restclient/anastasis_api_policy_meta_lookup.c @@ -0,0 +1,272 @@ +/* + This file is part of ANASTASIS + Copyright (C) 2022 Anastasis SARL + + ANASTASIS 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 2.1, + or (at your option) any later version. + + ANASTASIS 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 ANASTASIS; see the file COPYING.LGPL. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file restclient/anastasis_api_policy_meta_lookup.c + * @brief Implementation of the /policy/$POL/meta GET request + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <gnunet/gnunet_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * @brief A Meta Operation Handle + */ +struct ANASTASIS_PolicyMetaLookupOperation +{ + + /** + * The url for this request, including parameters. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_PolicyMetaLookupCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Public key of the account we are downloading from. + */ + struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; + + /** + * Maximum version to fetch. + */ + uint32_t max_version; + +}; + + +/** + * Process GET /policy/$POL/meta response + * + * @param cls our `struct ANASTASIS_PolicyMetaLookupOperation *` + * @param response_code HTTP status + * @param data response body, a `json_t *`, NULL on error + */ +static void +handle_policy_meta_lookup_finished (void *cls, + long response_code, + const void *response) +{ + struct ANASTASIS_PolicyMetaLookupOperation *plo = cls; + const json_t *json = response; + struct ANASTASIS_MetaDownloadDetails mdd = { + .http_status = response_code, + .response = json + }; + + plo->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from GET /policy\n"); + break; + case MHD_HTTP_OK: + { + size_t mlen = json_object_size (json); + + /* put a cap, as we will stack-allocate below and the + current service LIMITs the result to 1000 anyway; + could theoretically be increased in the future, but + then we should not put this onto the stack anymore... */ + if (mlen > 10000) + { + GNUNET_break (0); + response_code = 0; + break; + } + { + struct ANASTASIS_MetaDataEntry metas[GNUNET_NZL (mlen)]; + void *md[GNUNET_NZL (mlen)]; + size_t off = 0; + const char *label; + const json_t *val; + + memset (md, + 0, + sizeof (md)); + mdd.details.ok.metas = metas; + mdd.details.ok.metas_length = mlen; + json_object_foreach ((json_t *) json, + label, + val) + { + unsigned int ver; + char dummy; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_varsize ("meta", + &md[off], + &metas[off].meta_data_size), + GNUNET_JSON_spec_timestamp ("upload_time", + &metas[off].server_time), + GNUNET_JSON_spec_end () + }; + + if (1 != sscanf (label, + "%u%c", + &ver, + &dummy)) + { + GNUNET_break (0); + mdd.http_status = 0; + mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (val, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + mdd.http_status = 0; + mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + metas[off].version = (uint32_t) ver; + metas[off].meta_data = md[off]; + off++; + } + if (off < mlen) + { + GNUNET_break (0); + mdd.http_status = 0; + mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + for (size_t i = 0; i<off; i++) + GNUNET_free (md[i]); + break; + } + plo->cb (plo->cb_cls, + &mdd); + for (size_t i = 0; i<off; i++) + GNUNET_free (md[i]); + plo->cb = NULL; + } + ANASTASIS_policy_meta_lookup_cancel (plo); + return; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + 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_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "HTTP status for policy meta lookup is %u\n", + (unsigned int) response_code); + plo->cb (plo->cb_cls, + &mdd); + plo->cb = NULL; + ANASTASIS_policy_meta_lookup_cancel (plo); +} + + +struct ANASTASIS_PolicyMetaLookupOperation * +ANASTASIS_policy_meta_lookup ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub, + uint32_t max_version, + ANASTASIS_PolicyMetaLookupCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyMetaLookupOperation *plo; + CURL *eh; + char *path; + + // FIXME: pass 'max_version' in CURL request! + GNUNET_assert (NULL != cb); + plo = GNUNET_new (struct ANASTASIS_PolicyMetaLookupOperation); + plo->account_pub = *anastasis_pub; + { + char *acc_pub_str; + + acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub, + sizeof (*anastasis_pub)); + GNUNET_asprintf (&path, + "policy/%s/meta", + acc_pub_str); + GNUNET_free (acc_pub_str); + } + plo->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (plo->url); + GNUNET_assert (NULL != eh); + plo->cb = cb; + plo->cb_cls = cb_cls; + plo->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_policy_meta_lookup_finished, + plo); + return plo; +} + + +void +ANASTASIS_policy_meta_lookup_cancel ( + struct ANASTASIS_PolicyMetaLookupOperation *plo) +{ + if (NULL != plo->job) + { + GNUNET_CURL_job_cancel (plo->job); + plo->job = NULL; + } + GNUNET_free (plo->url); + GNUNET_free (plo); +} diff --git a/src/restclient/anastasis_api_policy_store.c b/src/restclient/anastasis_api_policy_store.c index 5d44094..3afee7d 100644 --- a/src/restclient/anastasis_api_policy_store.c +++ b/src/restclient/anastasis_api_policy_store.c @@ -1,18 +1,18 @@ /* This file is part of ANASTASIS - Copyright (C) 2014-2021 Anastasis SARL + Copyright (C) 2014-2022 Anastasis SARL ANASTASIS is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as + it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. ANASTASIS 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 Lesser General Public License for more details. + GNU General Public License for more details. - You should have received a copy of the GNU Lesser General Public + You should have received a copy of the GNU General Public License along with ANASTASIS; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> */ @@ -165,8 +165,7 @@ handle_policy_store_finished (void *cls, ud.us = ANASTASIS_US_SUCCESS; ud.details.success.curr_backup_hash = &pso->new_upload_hash; ud.details.success.policy_expiration - = GNUNET_TIME_absolute_add ( - GNUNET_TIME_UNIT_ZERO_ABS, + = GNUNET_TIME_relative_to_timestamp ( GNUNET_TIME_relative_multiply ( GNUNET_TIME_UNIT_SECONDS, expiration)); @@ -212,6 +211,10 @@ handle_policy_store_finished (void *cls, ud.us = ANASTASIS_US_PAYMENT_REQUIRED; ud.details.payment.payment_request = pso->pay_uri; break; + case MHD_HTTP_REQUEST_TIMEOUT: + ud.us = ANASTASIS_US_CLIENT_ERROR; + ud.ec = TALER_EC_ANASTASIS_PAYMENT_GENERIC_TIMEOUT; + break; case MHD_HTTP_PAYLOAD_TOO_LARGE: ud.us = ANASTASIS_US_CLIENT_ERROR; ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT; @@ -227,6 +230,11 @@ handle_policy_store_finished (void *cls, data_size); ud.us = ANASTASIS_US_SERVER_ERROR; break; + case MHD_HTTP_BAD_GATEWAY: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + ud.us = ANASTASIS_US_SERVER_ERROR; + break; default: ud.ec = TALER_JSON_get_error_code2 (data, data_size); @@ -348,6 +356,8 @@ ANASTASIS_policy_store ( const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv, const void *recovery_data, size_t recovery_data_size, + const void *recovery_meta_data, + size_t recovery_meta_data_size, uint32_t payment_years_requested, const struct ANASTASIS_PaymentSecretP *payment_secret, struct GNUNET_TIME_Relative payment_timeout, @@ -364,6 +374,11 @@ ANASTASIS_policy_store ( .purpose.size = htonl (sizeof (usp)) }; + if (NULL == recovery_meta_data) + { + GNUNET_break (0); + return NULL; + } tms = (unsigned long long) (payment_timeout.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); GNUNET_CRYPTO_hash (recovery_data, @@ -402,7 +417,7 @@ ANASTASIS_policy_store ( val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash, sizeof (struct GNUNET_HashCode)); GNUNET_asprintf (&hdr, - "%s: %s", + "%s: \"%s\"", MHD_HTTP_HEADER_IF_NONE_MATCH, val); GNUNET_free (val); @@ -417,6 +432,30 @@ ANASTASIS_policy_store ( } job_headers = ext; + /* Setup meta-data header */ + { + char *meta_val; + + meta_val = GNUNET_STRINGS_data_to_string_alloc ( + recovery_meta_data, + recovery_meta_data_size); + GNUNET_asprintf (&hdr, + "%s: %s", + ANASTASIS_HTTP_HEADER_POLICY_META_DATA, + meta_val); + GNUNET_free (meta_val); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + } + /* Setup Payment-Identifier header */ if (NULL != payment_secret) { diff --git a/src/restclient/anastasis_api_truth_challenge.c b/src/restclient/anastasis_api_truth_challenge.c new file mode 100644 index 0000000..7a39db5 --- /dev/null +++ b/src/restclient/anastasis_api_truth_challenge.c @@ -0,0 +1,456 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021, 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file restclient/anastasis_api_truth_challenge.c + * @brief Implementation of the POST /truth/$TID/challenge request on the client-side + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_curl_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * @brief A Contract Operation Handle + */ +struct ANASTASIS_TruthChallengeOperation +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_TruthChallengeCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Payment URI we received from the service, or NULL. + */ + char *pay_uri; + + /** + * Content type of the body. + */ + char *content_type; +}; + + +void +ANASTASIS_truth_challenge_cancel ( + struct ANASTASIS_TruthChallengeOperation *tco) +{ + if (NULL != tco->job) + { + GNUNET_CURL_job_cancel (tco->job); + tco->job = NULL; + } + GNUNET_free (tco->pay_uri); + GNUNET_free (tco->url); + GNUNET_free (tco->content_type); + TALER_curl_easy_post_finished (&tco->ctx); + GNUNET_free (tco); +} + + +/** + * Process POST /truth/$TID/challenge response + * + * @param cls our `struct ANASTASIS_TruthChallengeOperation *` + * @param response_code the HTTP status + * @param response parsed JSON result, NULL one rrro + */ +static void +handle_truth_challenge_finished (void *cls, + long response_code, + const void *response) +{ + struct ANASTASIS_TruthChallengeOperation *tco = cls; + const json_t *j = response; + struct ANASTASIS_TruthChallengeDetails tcd = { + .http_status = response_code, + .response = j + }; + + tco->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from POST /truth/$TID/challenge\n"); + tcd.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + const char *ct; + const char *tan_hint = NULL; + const char *filename = NULL; + const json_t *wire_details = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ( + "challenge_type", + &ct), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("tan_address_hint", + &tan_hint), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("filename", + &filename), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("wire_details", + &wire_details), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (0 == strcmp (ct, + "TAN_SENT")) + { + tcd.details.success.cs = ANASTASIS_CS_TAN_SENT; + tcd.details.success.details.tan_address_hint = tan_hint; + break; + } + if (0 == strcmp (ct, + "TAN_ALREADY_SENT")) + { + tcd.details.success.cs = ANASTASIS_CS_TAN_ALREADY_SENT; + break; + } + if ( (0 == strcmp (ct, + "FILE_WRITTEN")) && + (NULL != filename) ) + { + tcd.details.success.cs = ANASTASIS_CS_FILE_WRITTEN; + tcd.details.success.details.challenge_filename = filename; + break; + } + if ( (0 == strcmp (ct, + "IBAN_WIRE")) && + (NULL != wire_details) ) + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ( + "credit_iban", + &tcd.details.success.details.wire_funds.target_iban), + GNUNET_JSON_spec_uint64 ( + "answer_code", + &tcd.details.success.details.wire_funds.answer_code), + GNUNET_JSON_spec_string ( + "business_name", + &tcd.details.success.details.wire_funds.target_business_name), + GNUNET_JSON_spec_string ( + "wire_transfer_subject", + &tcd.details.success.details.wire_funds.wire_transfer_subject), + TALER_JSON_spec_amount_any ("challenge_amount", + &tcd.details.success.details.wire_funds. + amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (wire_details, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + tcd.details.success.cs = ANASTASIS_CS_WIRE_FUNDS; + tco->cb (tco->cb_cls, + &tcd); + ANASTASIS_truth_challenge_cancel (tco); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected challenge type `%s'\n", + ct); + json_dumpf (j, + stderr, + JSON_INDENT (2)); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == tco->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (tco->pay_uri, + &pd)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse `%s'\n", + tco->pay_uri); + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &tcd.details.payment_required.ps, + sizeof (tcd.details.payment_required.ps))) + { + GNUNET_break (0); + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + tcd.details.payment_required.pd = &pd; + tcd.details.payment_required.payment_request = tco->pay_uri; + tco->cb (tco->cb_cls, + &tcd); + TALER_MERCHANT_parse_pay_uri_free (&pd); + ANASTASIS_truth_challenge_cancel (tco); + return; + } + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, authentication required/failed */ + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_BAD_GATEWAY: + tcd.ec = TALER_JSON_get_error_code (j); + break; + default: + /* unexpected response code */ + tcd.ec = TALER_JSON_get_error_code (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to POST /truth/$TID/challenge\n", + (unsigned int) response_code, + (int) tcd.ec); + GNUNET_break (0); + break; + } + tco->cb (tco->cb_cls, + &tcd); + ANASTASIS_truth_challenge_cancel (tco); +} + + +/** + * Patch value in @a val, replacing new line with '\0'. + * + * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. + */ +static void +patch_value (char *val) +{ + size_t len; + + /* found location URI we care about! */ + len = strlen (val); + while ( (len > 0) && + ( ('\n' == val[len - 1]) || + ('\r' == val[len - 1]) ) ) + { + len--; + val[len] = '\0'; + } +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_StorePolicyOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_TruthChallengeOperation *tco = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_TALER)) + { + /* found payment URI we care about! */ + GNUNET_free (tco->pay_uri); + tco->pay_uri = GNUNET_strdup (hdr_val); + patch_value (tco->pay_uri); + } + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONTENT_TYPE)) + { + /* found location URI we care about! */ + GNUNET_free (tco->content_type); + tco->content_type = GNUNET_strdup (hdr_val); + patch_value (tco->content_type); + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_TruthChallengeOperation * +ANASTASIS_truth_challenge ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, + const struct ANASTASIS_PaymentSecretP *payment_secret, + ANASTASIS_TruthChallengeCallback cb, + void *cb_cls) +{ + struct ANASTASIS_TruthChallengeOperation *tco; + CURL *eh; + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("truth_decryption_key", + truth_key), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("payment_secret", + payment_secret))); + GNUNET_assert (NULL != body); + tco = GNUNET_new (struct ANASTASIS_TruthChallengeOperation); + tco->cb = cb; + tco->cb_cls = cb_cls; + { + char *path; + char *uuid_str; + + uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, + sizeof (*truth_uuid)); + GNUNET_asprintf (&path, + "truth/%s/challenge", + uuid_str); + GNUNET_free (uuid_str); + tco->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + eh = ANASTASIS_curl_easy_get_ (tco->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&tco->ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (tco->url); + GNUNET_free (tco); + return NULL; + } + json_decref (body); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + tco)); + tco->job = GNUNET_CURL_job_add2 (ctx, + eh, + tco->ctx.headers, + &handle_truth_challenge_finished, + tco); + return tco; +} + + +/* end of anastasis_api_truth_challenge.c */ diff --git a/src/restclient/anastasis_api_truth_solve.c b/src/restclient/anastasis_api_truth_solve.c new file mode 100644 index 0000000..9002a63 --- /dev/null +++ b/src/restclient/anastasis_api_truth_solve.c @@ -0,0 +1,437 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2021, 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file restclient/anastasis_api_truth_solve.c + * @brief Implementation of the POST /truth/$TID/solve request + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * @brief A Contract Operation Handle + */ +struct ANASTASIS_TruthSolveOperation +{ + /** + * The url for this request, including parameters. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_TruthSolveCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Payment URI we received from the service, or NULL. + */ + char *pay_uri; + + /** + * Content type of the body. + */ + char *content_type; +}; + + +void +ANASTASIS_truth_solve_cancel ( + struct ANASTASIS_TruthSolveOperation *tso) +{ + if (NULL != tso->job) + { + GNUNET_CURL_job_cancel (tso->job); + tso->job = NULL; + } + GNUNET_free (tso->pay_uri); + GNUNET_free (tso->url); + GNUNET_free (tso->content_type); + TALER_curl_easy_post_finished (&tso->ctx); + GNUNET_free (tso); +} + + +/** + * Process POST /truth/$TID/solve response + * + * @param cls our `struct ANASTASIS_TruthSolveOperation *` + * @param response_code the HTTP status + * @param data the body of the response + * @param data_size number of bytes in @a data + */ +static void +handle_truth_solve_finished (void *cls, + long response_code, + const void *data, + size_t data_size) +{ + struct ANASTASIS_TruthSolveOperation *tso = cls; + struct ANASTASIS_TruthSolveReply tsr = { + .http_status = response_code + }; + + tso->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from POST /truth/$TID/solve\n"); + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (sizeof (tsr.details.success.eks) != data_size) + { + GNUNET_break_op (0); + tsr.http_status = 0; + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + /* Success, call callback with all details! */ + memcpy (&tsr.details.success.eks, + data, + data_size); + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the anastasis server is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == tso->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (tso->pay_uri, + &pd)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse `%s'\n", + tso->pay_uri); + tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &tsr.details.payment_required.ps, + sizeof (tsr.details.payment_required.ps))) + { + GNUNET_break (0); + tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + tsr.details.payment_required.pd = &pd; + tsr.details.payment_required.payment_request = tso->pay_uri; + tso->cb (tso->cb_cls, + &tsr); + TALER_MERCHANT_parse_pay_uri_free (&pd); + ANASTASIS_truth_solve_cancel (tso); + return; + } + break; + case MHD_HTTP_FORBIDDEN: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_NOT_FOUND: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_REQUEST_TIMEOUT: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_TOO_MANY_REQUESTS: + { + json_t *reply; + + reply = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == reply) + { + GNUNET_break_op (0); + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ( + "request_limit", + &tsr.details.too_many_requests.request_limit), + GNUNET_JSON_spec_relative_time ( + "request_frequency", + &tsr.details.too_many_requests.request_frequency), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + json_decref (reply); + break; + } + json_decref (reply); + break; + } + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + case MHD_HTTP_BAD_GATEWAY: + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; + default: + /* unexpected response code */ + tsr.ec = TALER_JSON_get_error_code2 (data, + data_size); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to POST /truth/$TID/solve\n", + (unsigned int) response_code, + (int) tsr.ec); + break; + } + tso->cb (tso->cb_cls, + &tsr); + ANASTASIS_truth_solve_cancel (tso); +} + + +/** + * Patch value in @a val, replacing new line with '\0'. + * + * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. + */ +static void +patch_value (char *val) +{ + size_t len; + + /* found location URI we care about! */ + len = strlen (val); + while ( (len > 0) && + ( ('\n' == val[len - 1]) || + ('\r' == val[len - 1]) ) ) + { + len--; + val[len] = '\0'; + } +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct ANASTASIS_StorePolicyOperation *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct ANASTASIS_TruthSolveOperation *tso = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + ANASTASIS_HTTP_HEADER_TALER)) + { + /* found payment URI we care about! */ + GNUNET_free (tso->pay_uri); + tso->pay_uri = GNUNET_strdup (hdr_val); + patch_value (tso->pay_uri); + } + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONTENT_TYPE)) + { + /* found location URI we care about! */ + GNUNET_free (tso->content_type); + tso->content_type = GNUNET_strdup (hdr_val); + patch_value (tso->content_type); + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_TruthSolveOperation * +ANASTASIS_truth_solve ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, + const struct ANASTASIS_PaymentSecretP *payment_secret, + struct GNUNET_TIME_Relative timeout, + const struct GNUNET_HashCode *hashed_answer, + ANASTASIS_TruthSolveCallback cb, + void *cb_cls) +{ + struct ANASTASIS_TruthSolveOperation *tso; + CURL *eh; + char *path; + unsigned long long tms; + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("truth_decryption_key", + truth_key), + GNUNET_JSON_pack_data_auto ("h_response", + hashed_answer), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("payment_secret", + payment_secret))); + GNUNET_assert (NULL != body); + + tms = (unsigned long long) (timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + tso = GNUNET_new (struct ANASTASIS_TruthSolveOperation); + tso->cb = cb; + tso->cb_cls = cb_cls; + { + char *uuid_str; + + uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, + sizeof (*truth_uuid)); + GNUNET_asprintf (&path, + "truth/%s/solve", + uuid_str); + GNUNET_free (uuid_str); + } + { + char timeout_ms[32]; + + GNUNET_snprintf (timeout_ms, + sizeof (timeout_ms), + "%llu", + tms); + tso->url = TALER_url_join (backend_url, + path, + "timeout_ms", + (! GNUNET_TIME_relative_is_zero (timeout)) + ? timeout_ms + : NULL, + NULL); + } + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (tso->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&tso->ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (tso->url); + GNUNET_free (tso); + return NULL; + } + json_decref (body); + if (0 != tms) + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 5000))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + tso)); + tso->job = GNUNET_CURL_job_add_raw (ctx, + eh, + tso->ctx.headers, + &handle_truth_solve_finished, + tso); + return tso; +} + + +/* end of anastasis_api_truth_solve.c */ diff --git a/src/restclient/anastasis_api_truth_store.c b/src/restclient/anastasis_api_truth_store.c index 74b9238..855ad5a 100644 --- a/src/restclient/anastasis_api_truth_store.c +++ b/src/restclient/anastasis_api_truth_store.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -169,10 +169,18 @@ handle_truth_store_finished (void *cls, ud.ec = TALER_JSON_get_error_code2 (data, data_size); break; + case MHD_HTTP_BAD_GATEWAY: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; default: - GNUNET_break (0); ud.ec = TALER_JSON_get_error_code2 (data, data_size); + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected HTTP status code %u/%d\n", + (unsigned int) response_code, + ud.ec); break; } tso->cb (tso->cb_cls, @@ -296,7 +304,7 @@ ANASTASIS_truth_store ( json_t *truth_data; truth_data = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("keyshare_data", + GNUNET_JSON_pack_data_auto ("key_share_data", encrypted_keyshare), GNUNET_JSON_pack_string ("type", type), diff --git a/src/stasis/Makefile.am b/src/stasis/Makefile.am index 140f65e..0197f0e 100644 --- a/src/stasis/Makefile.am +++ b/src/stasis/Makefile.am @@ -16,9 +16,9 @@ endif sqldir = $(prefix)/share/anastasis/sql/ sql_DATA = \ - stasis-0000.sql \ + versioning.sql \ stasis-0001.sql \ - drop0001.sql + drop.sql pkgcfgdir = $(prefix)/share/anastasis/config.d/ @@ -59,16 +59,16 @@ libanastasisdb_la_LDFLAGS = \ libanastasis_plugin_db_postgres_la_SOURCES = \ plugin_anastasis_postgres.c -libanastasis_plugin_db_postgres_la_LIBADD = \ - $(LTLIBINTL) libanastasis_plugin_db_postgres_la_LDFLAGS = \ + $(ANASTASIS_PLUGIN_LDFLAGS) +libanastasis_plugin_db_postgres_la_LIBADD = \ + $(LTLIBINTL) \ $(top_builddir)/src/util/libanastasisutil.la \ - $(ANASTASIS_PLUGIN_LDFLAGS) \ - -lgnunetpq \ - -lpq \ -ltalerpq \ -ltalerutil \ + -lgnunetpq \ -lgnunetutil \ + -lpq \ $(XLIB) check_PROGRAMS = \ @@ -83,7 +83,6 @@ test_anastasis_db_postgres_LDFLAGS = \ -lgnunetpq \ -ltalerutil \ -ltalerpq \ - -luuid \ $(XLIB) AM_TESTS_ENVIRONMENT=export ANASTASIS_PREFIX=$${ANASTASIS_PREFIX:-@libdir@};export PATH=$${ANASTASIS_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; diff --git a/src/stasis/anastasis_db_plugin.c b/src/stasis/anastasis_db_plugin.c index 7edca5f..a7e55b9 100644 --- a/src/stasis/anastasis_db_plugin.c +++ b/src/stasis/anastasis_db_plugin.c @@ -3,7 +3,7 @@ Copyright (C) 2015, 2016 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY diff --git a/src/stasis/drop0001.sql b/src/stasis/drop.sql index c7697c0..357fa9d 100644 --- a/src/stasis/drop0001.sql +++ b/src/stasis/drop.sql @@ -1,6 +1,6 @@ -- -- This file is part of ANASTASIS --- Copyright (C) 2014--2020 Anastasis Systems SA +-- Copyright (C) 2014--2022 Anastasis Systems SA -- -- ANASTASIS 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 @@ -17,22 +17,15 @@ -- Everything in one big transaction BEGIN; --- This script DROPs all of the tables we create. --- --- Unlike the other SQL files, it SHOULD be updated to reflect the --- latest requirements for dropping tables. - --- Drops for 0001.sql -DROP TABLE IF EXISTS anastasis_truth CASCADE; -DROP TABLE IF EXISTS anastasis_user CASCADE; -DROP TABLE IF EXISTS anastasis_recdoc_payment; -DROP TABLE IF EXISTS anastasis_recoverydocument; -DROP TABLE IF EXISTS anastasis_challengecode; -DROP TABLE IF EXISTS anastasis_challenge_payment; -DROP TABLE IF EXISTS anastasis_auth_iban_in; +WITH xpatches AS ( + SELECT patch_name + FROM _v.patches + WHERE starts_with(patch_name,'stasis-') +) + SELECT _v.unregister_patch(xpatches.patch_name) + FROM xpatches; --- Unregister patch (0001.sql) -SELECT _v.unregister_patch('stasis-0001'); +DROP SCHEMA anastasis CASCADE; -- And we're out of here... COMMIT; diff --git a/src/stasis/plugin_anastasis_postgres.c b/src/stasis/plugin_anastasis_postgres.c index b1be081..9f4b969 100644 --- a/src/stasis/plugin_anastasis_postgres.c +++ b/src/stasis/plugin_anastasis_postgres.c @@ -1,9 +1,9 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -15,8 +15,7 @@ */ /** * @file stasis/plugin_anastasis_postgres.c - * @brief database helper functions for postgres used by the anastasis - * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @brief database helper functions for postgres used by GNU Anastasis * @author Christian Grothoff * @author Marcello Stanisci */ @@ -38,12 +37,6 @@ */ #define MAX_RETRIES 3 -/** - * Maximum value allowed for nonces. Limited to 2^52 to ensure the - * numeric value survives a conversion to float by JavaScript. - */ -#define NONCE_MAX_VALUE (1LLU << 52) - /** * Type of the "cls" argument given to each of the functions in @@ -90,16 +83,19 @@ postgres_drop_tables (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_Context *conn; + enum GNUNET_GenericReturnValue ret; conn = GNUNET_PQ_connect_with_cfg (pg->cfg, "stasis-postgres", - "drop", + NULL, NULL, NULL); if (NULL == conn) return GNUNET_SYSERR; + ret = GNUNET_PQ_exec_sql (conn, + "drop"); GNUNET_PQ_disconnect (conn); - return GNUNET_OK; + return ret; } @@ -114,11 +110,15 @@ postgres_create_tables (void *cls) { struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; conn = GNUNET_PQ_connect_with_cfg (pc->cfg, "stasis-postgres", "stasis-", - NULL, + es, NULL); if (NULL == conn) return GNUNET_SYSERR; @@ -134,7 +134,7 @@ postgres_create_tables (void *cls) * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static enum GNUNET_GenericReturnValue -postgres_connect (void *cls) +prepare_statements (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_PreparedStatement ps[] = { @@ -143,54 +143,44 @@ postgres_connect (void *cls) "(user_id" ",expiration_date" ") VALUES " - "($1, $2);", - 2), + "($1, $2);"), GNUNET_PQ_make_prepare ("do_commit", - "COMMIT", - 0), + "COMMIT"), GNUNET_PQ_make_prepare ("user_select", "SELECT" " expiration_date " "FROM anastasis_user" " WHERE user_id=$1" - " FOR UPDATE;", - 1), + " FOR UPDATE;"), GNUNET_PQ_make_prepare ("user_update", "UPDATE anastasis_user" " SET " " expiration_date=$1" - " WHERE user_id=$2;", - 2), + " WHERE user_id=$2;"), GNUNET_PQ_make_prepare ("recdoc_payment_insert", "INSERT INTO anastasis_recdoc_payment " "(user_id" ",post_counter" - ",amount_val" - ",amount_frac" + ",amount" ",payment_identifier" ",creation_date" ") VALUES " - "($1, $2, $3, $4, $5, $6);", - 6), + "($1, $2, $3, $4, $5);"), GNUNET_PQ_make_prepare ("challenge_payment_insert", "INSERT INTO anastasis_challenge_payment " "(truth_uuid" - ",amount_val" - ",amount_frac" + ",amount" ",payment_identifier" ",creation_date" ") VALUES " - "($1, $2, $3, $4, $5);", - 5), + "($1, $2, $3, $4);"), GNUNET_PQ_make_prepare ("truth_payment_insert", "INSERT INTO anastasis_truth_payment " "(truth_uuid" - ",amount_val" - ",amount_frac" + ",amount" ",expiration" ") VALUES " - "($1, $2, $3, $4);", - 4), + "($1, $2, $3);"), GNUNET_PQ_make_prepare ("recdoc_payment_done", "UPDATE anastasis_recdoc_payment " "SET" @@ -200,8 +190,7 @@ postgres_connect (void *cls) " AND" " user_id=$2" " AND" - " paid=FALSE;", - 2), + " paid=FALSE;"), GNUNET_PQ_make_prepare ("challenge_refund_update", "UPDATE anastasis_challenge_payment " "SET" @@ -211,8 +200,7 @@ postgres_connect (void *cls) " AND" " paid=TRUE" " AND" - " truth_uuid=$2;", - 2), + " truth_uuid=$2;"), GNUNET_PQ_make_prepare ("challenge_payment_done", "UPDATE anastasis_challenge_payment " "SET" @@ -224,43 +212,36 @@ postgres_connect (void *cls) " AND" " truth_uuid=$2" " AND" - " paid=FALSE;", - 2), + " paid=FALSE;"), GNUNET_PQ_make_prepare ("recdoc_payment_select", "SELECT" " creation_date" ",post_counter" - ",amount_val" - ",amount_frac" + ",amount" ",paid" " FROM anastasis_recdoc_payment" - " WHERE payment_identifier=$1;", - 1), + " WHERE payment_identifier=$1;"), GNUNET_PQ_make_prepare ("truth_payment_select", "SELECT" " expiration" " FROM anastasis_truth_payment" " WHERE truth_uuid=$1" - " AND expiration>$2;", - 2), + " AND expiration>$2;"), GNUNET_PQ_make_prepare ("challenge_payment_select", "SELECT" " creation_date" - ",amount_val" - ",amount_frac" + ",amount" ",paid" " FROM anastasis_challenge_payment" " WHERE payment_identifier=$1" " AND truth_uuid=$2" " AND refunded=FALSE" - " AND counter>0;", - 1), + " AND counter>0;"), GNUNET_PQ_make_prepare ("challenge_pending_payment_select", "SELECT" " creation_date" ",payment_identifier" - ",amount_val" - ",amount_frac" + ",amount" " FROM anastasis_challenge_payment" " WHERE" " paid=FALSE" @@ -269,29 +250,24 @@ postgres_connect (void *cls) " AND" " truth_uuid=$1" " AND" - " creation_date > $2;", - 1), + " creation_date > $2;"), GNUNET_PQ_make_prepare ("recdoc_payments_select", "SELECT" " user_id" ",payment_identifier" - ",amount_val" - ",amount_frac" + ",amount" " FROM anastasis_recdoc_payment" - " WHERE paid=FALSE;", - 0), + " WHERE paid=FALSE;"), GNUNET_PQ_make_prepare ("gc_accounts", "DELETE FROM anastasis_user " "WHERE" - " expiration_date < $1;", - 1), + " expiration_date < $1;"), GNUNET_PQ_make_prepare ("gc_recdoc_pending_payments", "DELETE FROM anastasis_recdoc_payment " "WHERE" " paid=FALSE" " AND" - " creation_date < $1;", - 1), + " creation_date < $1;"), GNUNET_PQ_make_prepare ("gc_challenge_pending_payments", "DELETE FROM anastasis_challenge_payment " "WHERE" @@ -299,8 +275,7 @@ postgres_connect (void *cls) " OR" " refunded=TRUE)" " AND" - " creation_date < $1;", - 1), + " creation_date < $1;"), GNUNET_PQ_make_prepare ("truth_insert", "INSERT INTO anastasis_truth " "(truth_uuid" @@ -310,32 +285,24 @@ postgres_connect (void *cls) ",truth_mime" ",expiration" ") VALUES " - "($1, $2, $3, $4, $5, $6);", - 6), - + "($1, $2, $3, $4, $5, $6);"), GNUNET_PQ_make_prepare ("test_auth_iban_payment", "SELECT" - " credit_val" - ",credit_frac" + " credit" ",wire_subject" " FROM anastasis_auth_iban_in" " WHERE debit_account_details=$1" - " AND execution_date>=$2;", - 2), + " AND execution_date>=$2;"), GNUNET_PQ_make_prepare ("store_auth_iban_payment_details", "INSERT INTO anastasis_auth_iban_in " "(wire_reference" ",wire_subject" - ",credit_val" - ",credit_frac" + ",credit" ",debit_account_details" ",credit_account_details" ",execution_date" ") VALUES " - "($1, $2, $3, $4, $5, $6, $7);", - 7), - - + "($1, $2, $3, $4, $5, $6);"), GNUNET_PQ_make_prepare ("recovery_document_insert", "INSERT INTO anastasis_recoverydocument " "(user_id" @@ -343,17 +310,27 @@ postgres_connect (void *cls) ",account_sig" ",recovery_data_hash" ",recovery_data" + ",recovery_meta_data" + ",creation_date" ") VALUES " - "($1, $2, $3, $4, $5);", - 5), + "($1, $2, $3, $4, $5, $6, $7);"), GNUNET_PQ_make_prepare ("truth_select", "SELECT " " method_name" ",encrypted_truth" ",truth_mime" " FROM anastasis_truth" - " WHERE truth_uuid =$1;", - 1), + " WHERE truth_uuid=$1;"), + GNUNET_PQ_make_prepare ("recoverydocument_select_meta", + "SELECT " + " version" + ",creation_date" + ",recovery_meta_data" + " FROM anastasis_recoverydocument" + " WHERE user_id=$1" + " AND version < $2" + " ORDER BY version DESC" + " LIMIT 1000;"), GNUNET_PQ_make_prepare ("latest_recoverydocument_select", "SELECT " " version" @@ -361,10 +338,9 @@ postgres_connect (void *cls) ",recovery_data_hash" ",recovery_data" " FROM anastasis_recoverydocument" - " WHERE user_id =$1 " + " WHERE user_id=$1" " ORDER BY version DESC" - " LIMIT 1;", - 1), + " LIMIT 1;"), GNUNET_PQ_make_prepare ("latest_recovery_version_select", "SELECT" " version" @@ -374,8 +350,7 @@ postgres_connect (void *cls) " JOIN anastasis_user USING (user_id)" " WHERE user_id=$1" " ORDER BY version DESC" - " LIMIT 1;", - 1), + " LIMIT 1;"), GNUNET_PQ_make_prepare ("recoverydocument_select", "SELECT " " account_sig" @@ -383,30 +358,26 @@ postgres_connect (void *cls) ",recovery_data" " FROM anastasis_recoverydocument" " WHERE user_id=$1" - " AND version=$2;", - 2), + " AND version=$2;"), GNUNET_PQ_make_prepare ("postcounter_select", "SELECT" " post_counter" " FROM anastasis_recdoc_payment" " WHERE user_id=$1" - " AND payment_identifier=$2;", - 2), + " AND payment_identifier=$2;"), GNUNET_PQ_make_prepare ("postcounter_update", - "UPDATE " - "anastasis_recdoc_payment " - "SET " - "post_counter=$1 " - "WHERE user_id =$2 " - "AND payment_identifier=$3;", - 3), + "UPDATE" + " anastasis_recdoc_payment" + " SET" + " post_counter=$1" + " WHERE user_id =$2" + " AND payment_identifier=$3;"), GNUNET_PQ_make_prepare ("key_share_select", "SELECT " "key_share_data " "FROM " "anastasis_truth " - "WHERE truth_uuid =$1;", - 1), + "WHERE truth_uuid =$1;"), GNUNET_PQ_make_prepare ("challengecode_insert", "INSERT INTO anastasis_challengecode " "(truth_uuid" @@ -415,8 +386,7 @@ postgres_connect (void *cls) ",expiration_date" ",retry_counter" ") VALUES " - "($1, $2, $3, $4, $5);", - 5), + "($1, $2, $3, $4, $5);"), GNUNET_PQ_make_prepare ("challengecode_select", "SELECT " " code" @@ -424,8 +394,7 @@ postgres_connect (void *cls) " FROM anastasis_challengecode" " WHERE truth_uuid=$1" " AND expiration_date > $2" - " AND retry_counter != 0;", - 2), + " AND retry_counter != 0;"), GNUNET_PQ_make_prepare ("challengecode_set_satisfied", "UPDATE anastasis_challengecode" " SET satisfied=TRUE" @@ -437,16 +406,14 @@ postgres_connect (void *cls) " WHERE truth_uuid=$1" " AND code=$2" " ORDER BY creation_date DESC" - " LIMIT 1);", - 2), + " LIMIT 1);"), GNUNET_PQ_make_prepare ("challengecode_test_satisfied", "SELECT 1 FROM anastasis_challengecode" " WHERE truth_uuid=$1" " AND satisfied=TRUE" " AND code=$2" " AND creation_date >= $3" - " LIMIT 1;", - 3), + " LIMIT 1;"), GNUNET_PQ_make_prepare ("challengecode_select_meta", "SELECT " " code" @@ -457,22 +424,19 @@ postgres_connect (void *cls) " AND expiration_date > $2" " AND creation_date > $3" " ORDER BY creation_date DESC" - " LIMIT 1;", - 2), + " LIMIT 1;"), GNUNET_PQ_make_prepare ("challengecode_update_retry", "UPDATE anastasis_challengecode" " SET retry_counter=retry_counter - 1" " WHERE truth_uuid=$1" " AND code=$2" - " AND retry_counter != 0;", - 1), + " AND retry_counter != 0;"), GNUNET_PQ_make_prepare ("challengepayment_dec_counter", "UPDATE anastasis_challenge_payment" " SET counter=counter - 1" " WHERE truth_uuid=$1" " AND payment_identifier=$2" - " AND counter > 0;", - 2), + " AND counter > 0;"), GNUNET_PQ_make_prepare ("challengecode_mark_sent", "UPDATE anastasis_challengecode" " SET retransmission_date=$3" @@ -484,32 +448,31 @@ postgres_connect (void *cls) " WHERE truth_uuid=$1" " AND code=$2" " ORDER BY creation_date DESC" - " LIMIT 1);", - 3), + " LIMIT 1);"), GNUNET_PQ_make_prepare ("get_last_auth_iban_payment", "SELECT " " wire_reference" " FROM anastasis_auth_iban_in" " WHERE credit_account_details=$1" " ORDER BY wire_reference DESC" - " LIMIT 1;", - 1), + " LIMIT 1;"), GNUNET_PQ_make_prepare ("gc_challengecodes", "DELETE FROM anastasis_challengecode " "WHERE " - "expiration_date < $1;", - 1), + "expiration_date < $1;"), GNUNET_PQ_PREPARED_STATEMENT_END }; - pg->conn = GNUNET_PQ_connect_with_cfg (pg->cfg, - "stasis-postgres", - NULL, - NULL, - ps); - if (NULL == pg->conn) - return GNUNET_SYSERR; - return GNUNET_OK; + { + enum GNUNET_GenericReturnValue ret; + + ret = GNUNET_PQ_prepare_statements (pg->conn, + ps); + if (GNUNET_OK != ret) + return ret; + pg->init = true; + return GNUNET_OK; + } } @@ -554,15 +517,19 @@ internal_setup (struct PostgresClosure *pg, "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), + GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), GNUNET_PQ_EXECUTE_STATEMENT_END }; #else - struct GNUNET_PQ_ExecuteStatement *es = NULL; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; #endif struct GNUNET_PQ_Context *db_conn; db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg, - "exchangedb-postgres", + "stasis-postgres", NULL, es, NULL); @@ -576,7 +543,7 @@ internal_setup (struct PostgresClosure *pg, return GNUNET_OK; if (skip_prepare) return GNUNET_OK; - return postgres_connect (pg); + return prepare_statements (pg); } @@ -635,7 +602,7 @@ postgres_preflight (void *cls) * must point to a constant * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue begin_transaction (void *cls, const char *name) { @@ -646,7 +613,8 @@ begin_transaction (void *cls, }; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); pg->transaction_name = name; if (GNUNET_OK != GNUNET_PQ_exec_statements (pg->conn, @@ -802,7 +770,8 @@ postgres_gc (void *cls, enum GNUNET_DB_QueryStatus qs; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "gc_accounts", params); @@ -836,6 +805,8 @@ postgres_store_recovery_document ( const struct GNUNET_HashCode *recovery_data_hash, const void *recovery_data, size_t recovery_data_size, + const void *recovery_meta_data, + size_t recovery_meta_data_size, const struct ANASTASIS_PaymentSecretP *payment_secret, uint32_t *version) { @@ -843,7 +814,8 @@ postgres_store_recovery_document ( enum GNUNET_DB_QueryStatus qs; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); for (unsigned int retry = 0; retry<MAX_RETRIES; retry++) { if (GNUNET_OK != @@ -1011,6 +983,8 @@ postgres_store_recovery_document ( /* finally, actually insert the recovery document */ { + struct GNUNET_TIME_Timestamp now + = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_uint32 (version), @@ -1018,6 +992,9 @@ postgres_store_recovery_document ( GNUNET_PQ_query_param_auto_from_type (recovery_data_hash), GNUNET_PQ_query_param_fixed_size (recovery_data, recovery_data_size), + GNUNET_PQ_query_param_fixed_size (recovery_meta_data, + recovery_meta_data_size), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; @@ -1052,7 +1029,8 @@ retry: /** - * Increment account lifetime. + * Increment account lifetime based on payment having been received. + * Does nothing if the payment is not new. * * @param cls closure * @param account_pub which account received a payment @@ -1067,11 +1045,16 @@ postgres_increment_lifetime ( const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, const struct ANASTASIS_PaymentSecretP *payment_identifier, struct GNUNET_TIME_Relative lifetime, - struct GNUNET_TIME_Absolute *paid_until) + struct GNUNET_TIME_Timestamp *paid_until) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Incrementing lifetime of account %s based on payment by %s\n", + TALER_B2S (account_pub), + GNUNET_TIME_relative2s (lifetime, + true)); check_connection (pg); for (unsigned int retries = 0; retries<MAX_RETRIES; retries++) { @@ -1096,14 +1079,16 @@ postgres_increment_lifetime ( { case GNUNET_DB_STATUS_HARD_ERROR: rollback (pg); - *paid_until = GNUNET_TIME_UNIT_ZERO_ABS; + *paid_until = GNUNET_TIME_UNIT_ZERO_TS; return qs; case GNUNET_DB_STATUS_SOFT_ERROR: goto retry; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Payment not new or payment request unknown. */ /* continued below */ break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* Payment just now marked as 'paid' */ /* continued below */ break; } @@ -1115,10 +1100,10 @@ postgres_increment_lifetime ( GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration_date", - &expiration), + GNUNET_PQ_result_spec_timestamp ("expiration_date", + &expiration), GNUNET_PQ_result_spec_end }; @@ -1146,14 +1131,18 @@ postgres_increment_lifetime ( /* user does not exist, create new one */ struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), - GNUNET_PQ_query_param_absolute_time (&expiration), + GNUNET_PQ_query_param_timestamp (&expiration), GNUNET_PQ_query_param_end }; - expiration = GNUNET_TIME_relative_to_absolute (lifetime); - GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - expiration.abs_value_us); + expiration = GNUNET_TIME_relative_to_timestamp (lifetime); + GNUNET_break (! GNUNET_TIME_absolute_is_never (expiration.abs_time)); *paid_until = expiration; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Creating new account %s with initial lifetime of %s\n", + TALER_B2S (account_pub), + GNUNET_TIME_relative2s (lifetime, + true)); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "user_insert", params); @@ -1162,28 +1151,36 @@ postgres_increment_lifetime ( case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - /* existing rec doc payment, return expiration */ + /* existing rec doc payment (payment replay), return + existing expiration */ *paid_until = expiration; rollback (pg); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment existed, lifetime of account %s unchanged at %s\n", TALER_B2S (account_pub), - GNUNET_STRINGS_absolute_time_to_string (*paid_until)); + GNUNET_TIME_timestamp2s (*paid_until)); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } else { - /* user exists, update expiration_date */ + /* user exists, payment is new, update expiration_date */ struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_absolute_time (&expiration), + GNUNET_PQ_query_param_timestamp (&expiration), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; - expiration = GNUNET_TIME_absolute_add (expiration, - lifetime); - GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - expiration.abs_value_us); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Incrementing lifetime of account %s by %s\n", + TALER_B2S (account_pub), + GNUNET_TIME_relative2s (lifetime, + true)); + expiration + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (expiration.abs_time, + lifetime)); + GNUNET_break (! GNUNET_TIME_absolute_is_never ( + expiration.abs_time)); *paid_until = expiration; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "user_update", @@ -1215,7 +1212,7 @@ postgres_increment_lifetime ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Incremented lifetime of account %s to %s\n", TALER_B2S (account_pub), - GNUNET_STRINGS_absolute_time_to_string (*paid_until)); + GNUNET_TIME_timestamp2s (*paid_until)); return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; retry: rollback (pg); @@ -1239,7 +1236,7 @@ postgres_update_lifetime ( void *cls, const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, const struct ANASTASIS_PaymentSecretP *payment_identifier, - struct GNUNET_TIME_Absolute eol) + struct GNUNET_TIME_Timestamp eol) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; @@ -1283,10 +1280,10 @@ postgres_update_lifetime ( GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration_date", - &expiration), + GNUNET_PQ_result_spec_timestamp ("expiration_date", + &expiration), GNUNET_PQ_result_spec_end }; @@ -1306,41 +1303,39 @@ postgres_update_lifetime ( /* user does not exist, create new one */ struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), - GNUNET_PQ_query_param_absolute_time (&eol), + GNUNET_PQ_query_param_timestamp (&eol), GNUNET_PQ_query_param_end }; - GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - eol.abs_value_us); + GNUNET_break (! GNUNET_TIME_absolute_is_never (eol.abs_time)); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "user_insert", params); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Created new account %s with expiration %s\n", TALER_B2S (account_pub), - GNUNET_STRINGS_absolute_time_to_string (eol)); + GNUNET_TIME_timestamp2s (eol)); } break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: { /* user exists, update expiration_date */ struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_absolute_time (&expiration), + GNUNET_PQ_query_param_timestamp (&expiration), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; - expiration = GNUNET_TIME_absolute_max (expiration, - eol); - GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != - expiration.abs_value_us); + expiration = GNUNET_TIME_timestamp_max (expiration, + eol); + GNUNET_break (! GNUNET_TIME_absolute_is_never (expiration.abs_time)); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "user_update", params); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Updated account %s to new expiration %s\n", TALER_B2S (account_pub), - GNUNET_STRINGS_absolute_time_to_string (expiration)); + GNUNET_TIME_timestamp2s (expiration)); } break; } @@ -1394,20 +1389,22 @@ postgres_record_recdoc_payment ( const struct TALER_Amount *amount) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_TIME_Absolute expiration; + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_uint32 (&post_counter), - TALER_PQ_query_param_amount (amount), + TALER_PQ_query_param_amount (pg->conn, + amount), GNUNET_PQ_query_param_auto_from_type (payment_secret), - GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); /* because of constraint at user_id, first we have to verify if user exists, and if not, create one */ @@ -1417,8 +1414,8 @@ postgres_record_recdoc_payment ( GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration_date", - &expiration), + GNUNET_PQ_result_spec_timestamp ("expiration_date", + &expiration), GNUNET_PQ_result_spec_end }; @@ -1437,11 +1434,11 @@ postgres_record_recdoc_payment ( case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: { /* create new user with short lifetime */ - struct GNUNET_TIME_Absolute exp - = GNUNET_TIME_relative_to_absolute (TRANSIENT_LIFETIME); + struct GNUNET_TIME_Timestamp exp + = GNUNET_TIME_relative_to_timestamp (TRANSIENT_LIFETIME); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), - GNUNET_PQ_query_param_absolute_time (&exp), + GNUNET_PQ_query_param_timestamp (&exp), GNUNET_PQ_query_param_end }; @@ -1463,7 +1460,7 @@ postgres_record_recdoc_payment ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Created new account %s with transient life until %s\n", TALER_B2S (account_pub), - GNUNET_STRINGS_absolute_time_to_string (exp)); + GNUNET_TIME_timestamp2s (exp)); break; } } @@ -1497,11 +1494,13 @@ postgres_record_truth_upload_payment ( struct GNUNET_TIME_Relative duration) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute exp = GNUNET_TIME_relative_to_absolute (duration); + struct GNUNET_TIME_Timestamp exp = GNUNET_TIME_relative_to_timestamp ( + duration); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (uuid), - TALER_PQ_query_param_amount (amount), - GNUNET_PQ_query_param_absolute_time (&exp), + TALER_PQ_query_param_amount (pg->conn, + amount), + GNUNET_PQ_query_param_timestamp (&exp), GNUNET_PQ_query_param_end }; @@ -1524,18 +1523,18 @@ static enum GNUNET_DB_QueryStatus postgres_check_truth_upload_paid ( void *cls, const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, - struct GNUNET_TIME_Absolute *paid_until) + struct GNUNET_TIME_Timestamp *paid_until) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (uuid), - GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration", - paid_until), + GNUNET_PQ_result_spec_timestamp ("expiration", + paid_until), GNUNET_PQ_result_spec_end }; @@ -1564,12 +1563,13 @@ postgres_record_challenge_payment ( const struct TALER_Amount *amount) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), - TALER_PQ_query_param_amount (amount), + TALER_PQ_query_param_amount (pg->conn, + amount), GNUNET_PQ_query_param_auto_from_type (payment_secret), - GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; @@ -1628,16 +1628,17 @@ postgres_record_auth_iban_payment ( const struct TALER_Amount *amount, const char *debit_account, const char *credit_account, - struct GNUNET_TIME_Absolute execution_date) + struct GNUNET_TIME_Timestamp execution_date) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&wire_reference), GNUNET_PQ_query_param_string (wire_subject), - TALER_PQ_query_param_amount (amount), + TALER_PQ_query_param_amount (pg->conn, + amount), GNUNET_PQ_query_param_string (debit_account), GNUNET_PQ_query_param_string (credit_account), - GNUNET_PQ_query_param_absolute_time (&execution_date), + GNUNET_PQ_query_param_timestamp (&execution_date), GNUNET_PQ_query_param_end }; @@ -1747,7 +1748,7 @@ static enum GNUNET_DB_QueryStatus postgres_test_auth_iban_payment ( void *cls, const char *debit_account, - struct GNUNET_TIME_Absolute earliest_date, + struct GNUNET_TIME_Timestamp earliest_date, ANASTASIS_DB_AuthIbanTransfercheck cb, void *cb_cls) { @@ -1759,7 +1760,7 @@ postgres_test_auth_iban_payment ( }; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (debit_account), - TALER_PQ_query_param_absolute_time (&earliest_date), + GNUNET_PQ_query_param_timestamp (&earliest_date), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; @@ -1930,7 +1931,7 @@ postgres_store_truth ( struct GNUNET_TIME_Relative truth_expiration) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute expiration = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Timestamp expiration; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), GNUNET_PQ_query_param_auto_from_type (key_share_data), @@ -1938,14 +1939,12 @@ postgres_store_truth ( GNUNET_PQ_query_param_fixed_size (encrypted_truth, encrypted_truth_size), GNUNET_PQ_query_param_string (mime_type), - TALER_PQ_query_param_absolute_time (&expiration), + GNUNET_PQ_query_param_timestamp (&expiration), GNUNET_PQ_query_param_end }; - expiration = GNUNET_TIME_absolute_add (expiration, - truth_expiration); - GNUNET_TIME_round_abs (&expiration); + expiration = GNUNET_TIME_relative_to_timestamp (truth_expiration); check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "truth_insert", @@ -2045,7 +2044,7 @@ enum ANASTASIS_DB_AccountStatus postgres_lookup_account ( void *cls, const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, - struct GNUNET_TIME_Absolute *paid_until, + struct GNUNET_TIME_Timestamp *paid_until, struct GNUNET_HashCode *recovery_data_hash, uint32_t *version) { @@ -2057,11 +2056,12 @@ postgres_lookup_account ( enum GNUNET_DB_QueryStatus qs; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); { struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration_date", - paid_until), + GNUNET_PQ_result_spec_timestamp ("expiration_date", + paid_until), GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash", recovery_data_hash), GNUNET_PQ_result_spec_uint32 ("version", @@ -2090,8 +2090,8 @@ postgres_lookup_account ( /* check if account exists */ { struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("expiration_date", - paid_until), + GNUNET_PQ_result_spec_timestamp ("expiration_date", + paid_until), GNUNET_PQ_result_spec_end }; @@ -2165,7 +2165,8 @@ postgres_get_latest_recovery_document ( }; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "latest_recoverydocument_select", params, @@ -2174,6 +2175,129 @@ postgres_get_latest_recovery_document ( /** + * Closure for meta_iterator(). + */ +struct MetaIteratorContext +{ + /** + * Function to call on each result. + */ + ANASTASIS_DB_RecoveryMetaCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Set to true on database failure. + */ + bool db_failure; +}; + + +/** + * Helper function for #postgres_get_recovery_meta_data(). + * To be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct MetaIteratorContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +meta_iterator (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct MetaIteratorContext *ctx = cls; + + for (unsigned int i = 0; i<num_results; i++) + { + uint32_t version; + void *meta_data; + size_t meta_data_size; + struct GNUNET_TIME_Timestamp ts; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint32 ("version", + &version), + GNUNET_PQ_result_spec_timestamp ("creation_date", + &ts), + GNUNET_PQ_result_spec_variable_size ("recovery_meta_data", + &meta_data, + &meta_data_size), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_GenericReturnValue ret; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->db_failure = true; + return; + } + ret = ctx->cb (ctx->cb_cls, + version, + ts, + meta_data, + meta_data_size); + GNUNET_PQ_cleanup_result (rs); + if (GNUNET_OK != ret) + break; + } +} + + +/** + * Fetch recovery document meta data for user. Returns + * meta data in descending order from @a max_version. + * The size of the result set may be limited. + * + * @param cls closure + * @param account_pub public key of the user's account + * @param max_version the maximum version number the user requests + * @param cb function to call on each result + * @param cb_cls closure for @a cb + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_get_recovery_meta_data ( + void *cls, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, + uint32_t max_version, + ANASTASIS_DB_RecoveryMetaCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct MetaIteratorContext ctx = { + .cb = cb, + .cb_cls = cb_cls + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (account_pub), + GNUNET_PQ_query_param_uint32 (&max_version), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "recoverydocument_select_meta", + params, + &meta_iterator, + &ctx); + if (qs < 0) + return qs; + if (ctx.db_failure) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} + + +/** * Fetch recovery document for user according given version. * * @param cls closure @@ -2301,6 +2425,10 @@ check_valid_code (void *cls, cvc->db_failure = true; return; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Found issued challenge %llu (client: %s)\n", + (unsigned long long) server_code, + GNUNET_h2s (cvc->hashed_code)); { struct GNUNET_HashCode shashed_code; @@ -2310,6 +2438,9 @@ check_valid_code (void *cls, GNUNET_memcmp (&shashed_code, cvc->hashed_code)) { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Challenge is valid challenge (%s)\n", + (0 != sat) ? "satisfied" : "not satisfied"); cvc->valid = true; cvc->code = server_code; cvc->satisfied = (0 != sat); @@ -2364,17 +2495,16 @@ postgres_verify_challenge_code ( .hashed_code = hashed_code, .pg = pg }; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), - TALER_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; *satisfied = false; check_connection (pg); - GNUNET_TIME_round_abs (&now); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "challengecode_select", params, @@ -2439,13 +2569,13 @@ postgres_test_challenge_code_satisfied ( void *cls, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, const uint64_t code, - struct GNUNET_TIME_Absolute after) + struct GNUNET_TIME_Timestamp after) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), GNUNET_PQ_query_param_uint64 (&code), - GNUNET_PQ_query_param_absolute_time (&after), + GNUNET_PQ_query_param_timestamp (&after), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -2475,12 +2605,13 @@ postgres_lookup_challenge_payment ( { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_TIME_Absolute recent - = GNUNET_TIME_absolute_subtract (now, - ANASTASIS_CHALLENGE_OFFER_LIFETIME); + struct GNUNET_TIME_Timestamp recent + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_subtract (now, + ANASTASIS_CHALLENGE_OFFER_LIFETIME)); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), - GNUNET_PQ_query_param_absolute_time (&recent), + GNUNET_PQ_query_param_timestamp (&recent), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -2547,20 +2678,18 @@ postgres_create_challenge_code ( struct GNUNET_TIME_Relative rotation_period, struct GNUNET_TIME_Relative validity_period, uint32_t retry_counter, - struct GNUNET_TIME_Absolute *retransmission_date, + struct GNUNET_TIME_Timestamp *retransmission_date, uint64_t *code) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); - struct GNUNET_TIME_Absolute expiration_date; + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + struct GNUNET_TIME_Timestamp expiration_date; struct GNUNET_TIME_Absolute ex_rot; check_connection (pg); - GNUNET_TIME_round_abs (&now); - expiration_date = GNUNET_TIME_absolute_add (now, - validity_period); - ex_rot = GNUNET_TIME_absolute_subtract (now, + expiration_date = GNUNET_TIME_relative_to_timestamp (validity_period); + ex_rot = GNUNET_TIME_absolute_subtract (now.abs_time, rotation_period); for (unsigned int retries = 0; retries<MAX_RETRIES; retries++) { @@ -2576,8 +2705,8 @@ postgres_create_challenge_code ( uint32_t old_retry_counter; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), - TALER_PQ_query_param_absolute_time (&now), - TALER_PQ_query_param_absolute_time (&ex_rot), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_absolute_time (&ex_rot), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -2585,8 +2714,8 @@ postgres_create_challenge_code ( code), GNUNET_PQ_result_spec_uint32 ("retry_counter", &old_retry_counter), - GNUNET_PQ_result_spec_absolute_time ("retransmission_date", - retransmission_date), + GNUNET_PQ_result_spec_timestamp ("retransmission_date", + retransmission_date), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; @@ -2613,26 +2742,27 @@ postgres_create_challenge_code ( rollback (pg); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Active challenge %llu has zero tries left, refusing to create another one\n", - (unsigned long long) code); + (unsigned long long) *code); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } rollback (pg); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Active challenge has %u tries left, returning old challenge\n", - (unsigned int) old_retry_counter); + "Active challenge has %u tries left, returning old challenge %llu\n", + (unsigned int) old_retry_counter, + (unsigned long long) *code); return qs; } } *code = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, - NONCE_MAX_VALUE); - *retransmission_date = GNUNET_TIME_UNIT_ZERO_ABS; + ANASTASIS_PIN_MAX_VALUE); + *retransmission_date = GNUNET_TIME_UNIT_ZERO_TS; { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), GNUNET_PQ_query_param_uint64 (code), - TALER_PQ_query_param_absolute_time (&now), - TALER_PQ_query_param_absolute_time (&expiration_date), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_timestamp (&expiration_date), GNUNET_PQ_query_param_uint32 (&retry_counter), GNUNET_PQ_query_param_end }; @@ -2691,22 +2821,24 @@ postgres_mark_challenge_sent ( check_connection (pg); { - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp now; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), GNUNET_PQ_query_param_uint64 (&code), - TALER_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_end }; - now = GNUNET_TIME_absolute_get (); - GNUNET_TIME_round_abs (&now); + now = GNUNET_TIME_timestamp_get (); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "challengecode_mark_sent", params); if (qs <= 0) return qs; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Marking challenge %llu as issued\n", + (unsigned long long) code); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (truth_uuid), @@ -2733,14 +2865,15 @@ enum GNUNET_DB_QueryStatus postgres_challenge_gc (void *cls) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute time_now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Timestamp time_now = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_absolute_time (&time_now), + GNUNET_PQ_query_param_timestamp (&time_now), GNUNET_PQ_query_param_end }; check_connection (pg); - postgres_preflight (pg); + GNUNET_break (GNUNET_OK == + postgres_preflight (pg)); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "gc_challengecodes", params); @@ -2777,7 +2910,8 @@ libanastasis_plugin_db_postgres_init (void *cls) } plugin = GNUNET_new (struct ANASTASIS_DatabasePlugin); plugin->cls = pg; - plugin->connect = &postgres_connect; + /* FIXME: Should this be the same? */ + plugin->connect = &postgres_preflight; plugin->create_tables = &postgres_create_tables; plugin->drop_tables = &postgres_drop_tables; plugin->gc = &postgres_gc; @@ -2793,6 +2927,7 @@ libanastasis_plugin_db_postgres_init (void *cls) plugin->get_escrow_challenge = &postgres_get_escrow_challenge; plugin->get_key_share = &postgres_get_key_share; plugin->get_latest_recovery_document = &postgres_get_latest_recovery_document; + plugin->get_recovery_meta_data = &postgres_get_recovery_meta_data; plugin->get_recovery_document = &postgres_get_recovery_document; plugin->lookup_account = &postgres_lookup_account; plugin->check_payment_identifier = &postgres_check_payment_identifier; diff --git a/src/stasis/stasis-0001.sql b/src/stasis/stasis-0001.sql index e0ebfa6..fe08cdc 100644 --- a/src/stasis/stasis-0001.sql +++ b/src/stasis/stasis-0001.sql @@ -1,6 +1,6 @@ -- -- This file is part of Anastasis --- Copyright (C) 2020, 2021 Anastasis SARL SA +-- Copyright (C) 2020, 2021, 2022, 2023 Anastasis SARL SA -- -- ANASTASIS 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 @@ -20,30 +20,41 @@ BEGIN; -- Check patch versioning is in place. SELECT _v.register_patch('stasis-0001', NULL, NULL); +CREATE SCHEMA anastasis; +COMMENT ON SCHEMA anastasis IS 'anastasis backend data'; + +SET search_path TO anastasis; + + +CREATE TYPE taler_amount + AS + (val INT8 + ,frac INT4 + ); +COMMENT ON TYPE taler_amount + IS 'Stores an amount, fraction is in units of 1/100000000 of the base value'; + CREATE TABLE IF NOT EXISTS anastasis_truth_payment (truth_uuid BYTEA PRIMARY KEY CHECK(LENGTH(truth_uuid)=32), - amount_val INT8 NOT NULL, - amount_frac INT4 NOT NULL, + amount taler_amount NOT NULL, expiration INT8 NOT NULL); COMMENT ON TABLE anastasis_truth_payment IS 'Records about payments for truth uploads'; COMMENT ON COLUMN anastasis_truth_payment.truth_uuid IS 'Identifier of the truth'; -COMMENT ON COLUMN anastasis_truth_payment.amount_val +COMMENT ON COLUMN anastasis_truth_payment.amount IS 'Amount we were paid'; -COMMENT ON COLUMN anastasis_truth_payment.amount_frac - IS 'Amount we were paid fraction'; COMMENT ON COLUMN anastasis_truth_payment.expiration IS 'At which date will the truth payment expire'; CREATE TABLE IF NOT EXISTS anastasis_truth (truth_uuid BYTEA PRIMARY KEY CHECK(LENGTH(truth_uuid)=32), - key_share_data BYTEA CHECK(LENGTH(key_share_data)=80) NOT NULL, - method_name VARCHAR NOT NULL, + key_share_data BYTEA CHECK(LENGTH(key_share_data)=72) NOT NULL, + method_name TEXT NOT NULL, encrypted_truth BYTEA NOT NULL, - truth_mime VARCHAR NOT NULL, + truth_mime TEXT NOT NULL, expiration INT8 NOT NULL); COMMENT ON TABLE anastasis_truth IS 'Truth data is needed to authenticate clients during recovery'; @@ -73,11 +84,10 @@ COMMENT ON COLUMN anastasis_user.expiration_date CREATE TABLE IF NOT EXISTS anastasis_recdoc_payment - (payment_id BIGSERIAL PRIMARY KEY, + (payment_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id BYTEA NOT NULL REFERENCES anastasis_user(user_id), post_counter INT4 NOT NULL DEFAULT 0 CHECK(post_counter >= 0), - amount_val INT8 NOT NULL, - amount_frac INT4 NOT NULL, + amount taler_amount NOT NULL, payment_identifier BYTEA NOT NULL CHECK(LENGTH(payment_identifier)=32), creation_date INT8 NOT NULL, paid BOOLEAN NOT NULL DEFAULT FALSE); @@ -89,10 +99,8 @@ COMMENT ON COLUMN anastasis_recdoc_payment.user_id IS 'Link to the corresponding user who paid'; COMMENT ON COLUMN anastasis_recdoc_payment.post_counter IS 'For how many posts does the user pay'; -COMMENT ON COLUMN anastasis_recdoc_payment.amount_val +COMMENT ON COLUMN anastasis_recdoc_payment.amount IS 'Amount we were paid'; -COMMENT ON COLUMN anastasis_recdoc_payment.amount_frac - IS 'Amount we were paid fraction'; COMMENT ON COLUMN anastasis_recdoc_payment.payment_identifier IS 'Payment identifier which the user has to provide'; COMMENT ON COLUMN anastasis_recdoc_payment.creation_date @@ -102,10 +110,9 @@ COMMENT ON COLUMN anastasis_recdoc_payment.paid CREATE TABLE IF NOT EXISTS anastasis_challenge_payment - (payment_id BIGSERIAL PRIMARY KEY, + (payment_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, truth_uuid BYTEA CHECK(LENGTH(truth_uuid)=32) NOT NULL, - amount_val INT8 NOT NULL, - amount_frac INT4 NOT NULL, + amount taler_amount NOT NULL, payment_identifier BYTEA NOT NULL CHECK(LENGTH(payment_identifier)=32), creation_date INT8 NOT NULL, counter INT4 NOT NULL DEFAULT 3, @@ -118,10 +125,8 @@ COMMENT ON COLUMN anastasis_challenge_payment.payment_id IS 'Serial number which identifies the payment'; COMMENT ON COLUMN anastasis_challenge_payment.truth_uuid IS 'Link to the corresponding challenge which is paid'; -COMMENT ON COLUMN anastasis_challenge_payment.amount_val +COMMENT ON COLUMN anastasis_challenge_payment.amount IS 'Amount we were paid'; -COMMENT ON COLUMN anastasis_challenge_payment.amount_frac - IS 'Amount we were paid fraction'; COMMENT ON COLUMN anastasis_challenge_payment.payment_identifier IS 'Payment identifier which the user has to provide'; COMMENT ON COLUMN anastasis_challenge_payment.counter @@ -140,6 +145,8 @@ CREATE TABLE IF NOT EXISTS anastasis_recoverydocument account_sig BYTEA NOT NULL CHECK(LENGTH(account_sig)=64), recovery_data_hash BYTEA NOT NULL CHECK(length(recovery_data_hash)=64), recovery_data BYTEA NOT NULL, + recovery_meta_data BYTEA NOT NULL, + creation_date INT8 NOT NULL, PRIMARY KEY (user_id, version)); COMMENT ON TABLE anastasis_recoverydocument IS 'Stores a recovery document which contains the policy and the encrypted core secret'; @@ -151,8 +158,12 @@ COMMENT ON COLUMN anastasis_recoverydocument.account_sig IS 'Signature of the recovery document'; COMMENT ON COLUMN anastasis_recoverydocument.recovery_data_hash IS 'Hash of the recovery document to prevent unnecessary uploads'; +COMMENT ON COLUMN anastasis_recoverydocument.creation_date + IS 'Creation date of the recovery document (when it was uploaded)'; COMMENT ON COLUMN anastasis_recoverydocument.recovery_data IS 'Contains the encrypted policy and core secret'; +COMMENT ON COLUMN anastasis_recoverydocument.recovery_meta_data + IS 'Contains an encrypted human-readable and sometimes user-generated description of the backup'; CREATE TABLE IF NOT EXISTS anastasis_challengecode @@ -194,11 +205,10 @@ COMMENT ON INDEX anastasis_challengecode_expiration_index CREATE TABLE IF NOT EXISTS anastasis_auth_iban_in - (auth_in_serial_id BIGSERIAL UNIQUE + (auth_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE ,wire_reference INT8 NOT NULL PRIMARY KEY ,wire_subject TEXT NOT NULL - ,credit_val INT8 NOT NULL - ,credit_frac INT4 NOT NULL + ,credit taler_amount NOT NULL ,debit_account_details TEXT NOT NULL ,credit_account_details TEXT NOT NULL ,execution_date INT8 NOT NULL @@ -209,6 +219,8 @@ COMMENT ON COLUMN anastasis_auth_iban_in.wire_reference IS 'Unique number identifying the wire transfer in LibEuFin/Nexus'; COMMENT ON COLUMN anastasis_auth_iban_in.wire_subject IS 'For authentication, this contains the code, but also additional text'; +COMMENT ON COLUMN anastasis_auth_iban_in.credit + IS 'Amount we were credited'; COMMENT ON COLUMN anastasis_auth_iban_in.execution_date IS 'Used both for (theoretical) garbage collection and to see if the transfer happened on time'; COMMENT ON COLUMN anastasis_auth_iban_in.credit_account_details diff --git a/src/stasis/test_anastasis_db.c b/src/stasis/test_anastasis_db.c index 1ec9770..5ad29bc 100644 --- a/src/stasis/test_anastasis_db.c +++ b/src/stasis/test_anastasis_db.c @@ -3,7 +3,7 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY @@ -151,7 +151,7 @@ run (void *cls) &paymentSecretP, &amount)); { - struct GNUNET_TIME_Absolute res_time; + struct GNUNET_TIME_Timestamp res_time; FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != plugin->increment_lifetime (plugin->cls, @@ -192,11 +192,13 @@ run (void *cls) &recoveryDataHash, recovery_data, strlen (recovery_data), + "meta-data", + strlen ("meta-data"), &paymentSecretP, &docVersion)); { uint32_t vrs; - struct GNUNET_TIME_Absolute exp; + struct GNUNET_TIME_Timestamp exp; FAILIF (ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED != plugin->lookup_account (plugin->cls, @@ -240,7 +242,7 @@ run (void *cls) GNUNET_free (res_recovery_data); { - struct GNUNET_TIME_Absolute rt; + struct GNUNET_TIME_Timestamp rt; FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != plugin->create_challenge_code (plugin->cls, @@ -250,10 +252,10 @@ run (void *cls) 3, /* retry counter */ &rt, &challenge_code)); - FAILIF (0 != rt.abs_value_us); + FAILIF (! GNUNET_TIME_absolute_is_zero (rt.abs_time)); } { - struct GNUNET_TIME_Absolute rt; + struct GNUNET_TIME_Timestamp rt; uint64_t c2; FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != diff --git a/src/stasis/stasis-0000.sql b/src/stasis/versioning.sql index 116f409..444cf95 100644 --- a/src/stasis/stasis-0000.sql +++ b/src/stasis/versioning.sql @@ -146,12 +146,13 @@ BEGIN; + -- This file adds versioning support to database it will be loaded to. -- It requires that PL/pgSQL is already loaded - will raise exception otherwise. -- All versioning "stuff" (tables, functions) is in "_v" schema. -- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows). --- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling. +-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling CREATE SCHEMA IF NOT EXISTS _v; COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.'; diff --git a/src/testing/.gitignore b/src/testing/.gitignore index a6eb294..9ac5ba4 100644 --- a/src/testing/.gitignore +++ b/src/testing/.gitignore @@ -1,3 +1,6 @@ test_anastasis test_anastasisrest_api -test_anastasis_api_home/.local/share/taler/crypto-* +test_anastasis_api_home/taler/exchange-secmod-* +test_anastasis_api_home/taler/auditor/ +test_anastasis_api_home/taler/exchange/offline-keys/secm_tofus.pub +test_anastasis_api.conf.edited diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 8fc710b..22162d3 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -14,29 +14,18 @@ libanastasistesting_la_LDFLAGS = \ -no-undefined libanastasistesting_la_SOURCES = \ testing_api_cmd_policy_store.c \ + testing_api_cmd_truth_challenge.c \ + testing_api_cmd_truth_solve.c \ testing_api_cmd_truth_store.c \ testing_api_cmd_policy_lookup.c \ - testing_api_cmd_keyshare_lookup.c \ testing_api_cmd_config.c \ testing_api_helpers.c \ - testing_api_trait_account_pub.c \ - testing_api_trait_account_priv.c \ - testing_api_trait_eks.c \ - testing_api_trait_payment_secret.c \ - testing_api_trait_truth_key.c \ - testing_api_trait_truth_uuid.c \ - testing_api_trait_hash.c \ - testing_api_trait_salt.c \ - testing_api_trait_code.c \ + testing_api_traits.c \ testing_cmd_truth_upload.c \ testing_cmd_policy_create.c \ testing_cmd_secret_share.c \ testing_cmd_recover_secret.c \ - testing_cmd_challenge_answer.c \ - testing_trait_truth.c \ - testing_trait_policy.c \ - testing_trait_core_secret.c \ - testing_trait_challenge.c + testing_cmd_challenge_answer.c libanastasistesting_la_LIBADD = \ $(top_builddir)/src/restclient/libanastasisrest.la \ $(top_builddir)/src/lib/libanastasis.la \ @@ -49,7 +38,6 @@ libanastasistesting_la_LIBADD = \ -lgnunetjson \ -lgnunetutil \ -ljansson \ - -luuid \ -ltalertesting \ $(XLIB) @@ -83,9 +71,8 @@ test_anastasis_LDADD = \ EXTRA_DIST = \ test_anastasis_api.conf \ - test_anastasis_api_home/.config/taler/exchange/account-2.json \ - test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv \ + test_anastasis_api_home/taler/exchange/offline-keys/master.priv \ sms_authentication.sh MOSTLYCLEANFILES = \ - test_anastasis_api_home/.local/share/taler/exchange/offline-keys/secm_tofus.pub + test_anastasis_api_home/taler/exchange/offline-keys/secm_tofus.pub diff --git a/src/testing/test_anastasis.c b/src/testing/test_anastasis.c index f821f20..f28d9a9 100644 --- a/src/testing/test_anastasis.c +++ b/src/testing/test_anastasis.c @@ -1,16 +1,16 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020-2023 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -52,34 +52,29 @@ #define MERCHANT_ACCOUNT_NAME "3" /** - * Configuration of the bank. + * Credentials for the test. */ -static struct TALER_TESTING_BankConfiguration bc; - -/** - * Configuration of the exchange. - */ -static struct TALER_TESTING_ExchangeConfiguration ec; +static struct TALER_TESTING_Credentials cred; /** * Payto URI of the customer (payer). */ -static char *payer_payto; +static const char *payer_payto; /** * Payto URI of the exchange (escrow account). */ -static char *exchange_payto; +static const char *exchange_payto; /** * Payto URI of the merchant (receiver). */ -static char *merchant_payto; +static const char *merchant_payto; /** * Merchant base URL. */ -static char *merchant_url; +static const char *merchant_url; /** * Anastasis base URL. @@ -92,11 +87,6 @@ static char *anastasis_url; static char *file_secret; /** - * Merchant process. - */ -static struct GNUNET_OS_Process *merchantd; - -/** * Anastasis process. */ static struct GNUNET_OS_Process *anastasisd; @@ -135,7 +125,7 @@ cmd_transfer_to_exchange (const char *label, { return TALER_TESTING_cmd_admin_add_incoming (label, amount, - &bc.exchange_auth, + &cred.ba, payer_payto); } @@ -164,10 +154,12 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", "EUR:5", + 0, /* age */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", "create-reserve-1", "EUR:5", + 0, /* age */ MHD_HTTP_OK), /** * Check the reserve is depleted. @@ -278,7 +270,7 @@ run (void *cls, 0, /* challenge index */ "SomeTruth1", 0, /* mode */ - ANASTASIS_CHALLENGE_STATUS_SOLVED), + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED), #if 0 ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-2", NULL, /* payment ref */ @@ -286,13 +278,13 @@ run (void *cls, 1, /* challenge index */ "SomeTruth2", 0, /* mode */ - ANASTASIS_CHALLENGE_STATUS_SOLVED), + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED), #endif ANASTASIS_TESTING_cmd_challenge_start ("challenge-start-3-pay", NULL, /* payment ref */ "recover-secret-1", 2, /* challenge index */ - ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED), + ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED), TALER_TESTING_cmd_merchant_claim_order ("fetch-challenge-pay-proposal", merchant_url, MHD_HTTP_OK, @@ -310,14 +302,14 @@ run (void *cls, "challenge-start-3-pay", /* payment ref */ "recover-secret-1", 2, /* challenge index */ - ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS), + ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED), ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-3", "challenge-start-3-pay", /* payment ref */ "recover-secret-1", 2, /* challenge index */ "challenge-start-3-paid", /* answer */ 1, /* mode */ - ANASTASIS_CHALLENGE_STATUS_SOLVED), + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED), ANASTASIS_TESTING_cmd_recover_secret_finish ("recover-finish-1", "recover-secret-1", GNUNET_TIME_UNIT_SECONDS), @@ -326,27 +318,29 @@ run (void *cls, struct TALER_TESTING_Command commands[] = { /* general setup */ - TALER_TESTING_cmd_auditor_add ("add-auditor-OK", - MHD_HTTP_NO_CONTENT, - false), - TALER_TESTING_cmd_wire_add ("add-wire-account", - "payto://x-taler-bank/localhost/2", - MHD_HTTP_NO_CONTENT, - false), - TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys", - CONFIG_FILE), - TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees", - CONFIG_FILE, - "EUR:0.01", - "EUR:0.01"), - TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys", - 1), + TALER_TESTING_cmd_run_fakebank ("run-fakebank", + cred.cfg, + "exchange-account-exchange"), + TALER_TESTING_cmd_system_start ("start-taler", + CONFIG_FILE, + "-em", + "-u", "exchange-account-exchange", + NULL), + TALER_TESTING_cmd_get_exchange ("get-exchange", + cred.cfg, + NULL, + true, + true), TALER_TESTING_cmd_merchant_post_instances ("instance-create-default", merchant_url, "default", - merchant_payto, - "EUR", MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_merchant_post_account ( + "instance-create-default-account", + merchant_url, + merchant_payto, + NULL, NULL, + MHD_HTTP_OK), TALER_TESTING_cmd_batch ("pay", pay), TALER_TESTING_cmd_batch ("anastasis", @@ -354,9 +348,8 @@ run (void *cls, TALER_TESTING_cmd_end () }; - TALER_TESTING_run_with_fakebank (is, - commands, - bc.exchange_auth.wire_gateway_url); + TALER_TESTING_run (is, + commands); } @@ -364,19 +357,7 @@ int main (int argc, char *const *argv) { - unsigned int ret; - /* These environment variables get in the way... */ - unsetenv ("XDG_DATA_HOME"); - unsetenv ("XDG_CONFIG_HOME"); - - GNUNET_log_setup ("test-anastasis", - "DEBUG", - NULL); - if (GNUNET_OK != - TALER_TESTING_prepare_fakebank (CONFIG_FILE, - "exchange-account-exchange", - &bc)) - return 77; + int ret; { char dir[] = "/tmp/test-anastasis-file-XXXXXX"; @@ -391,73 +372,42 @@ main (int argc, "%s/.secret", dir); } - id_data = ANASTASIS_TESTING_make_id_data_example ( - "MaxMuster123456789"); - payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME); - exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME); - merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME); - if (NULL == - (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE))) - return 77; - TALER_TESTING_cleanup_files (CONFIG_FILE); + id_data = ANASTASIS_TESTING_make_id_data_example ("MaxMuster123456789"); + payer_payto = + "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME + "?receiver-name=62"; + exchange_payto = + "payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME + "?receiver-name=exchange"; + merchant_payto = + "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME + "?receiver-name=merchant"; + merchant_url = "http://localhost:8080/"; if (NULL == (anastasis_url = ANASTASIS_TESTING_prepare_anastasis (CONFIG_FILE))) return 77; - TALER_TESTING_cleanup_files (CONFIG_FILE); - - switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, - GNUNET_YES, - &ec)) + if (NULL == (anastasisd = + ANASTASIS_TESTING_run_anastasis (CONFIG_FILE, + anastasis_url))) { - case GNUNET_SYSERR: - GNUNET_break (0); - return 1; - case GNUNET_NO: - return 77; - case GNUNET_OK: - if (NULL == (merchantd = - TALER_TESTING_run_merchant (CONFIG_FILE, - merchant_url))) - { - GNUNET_break (0); - return 1; - } - if (NULL == (anastasisd = - ANASTASIS_TESTING_run_anastasis (CONFIG_FILE, - anastasis_url))) - { - GNUNET_break (0); - GNUNET_OS_process_kill (merchantd, - SIGTERM); - GNUNET_OS_process_wait (merchantd); - GNUNET_OS_process_destroy (merchantd); - - return 1; - } - ret = TALER_TESTING_setup_with_exchange (&run, - NULL, - CONFIG_FILE); - - GNUNET_OS_process_kill (merchantd, - SIGTERM); - GNUNET_OS_process_kill (anastasisd, - SIGTERM); - GNUNET_OS_process_wait (merchantd); - GNUNET_OS_process_wait (anastasisd); - GNUNET_OS_process_destroy (merchantd); - GNUNET_OS_process_destroy (anastasisd); - GNUNET_free (merchant_url); - GNUNET_free (anastasis_url); - - if (GNUNET_OK != ret) - return 1; - break; - default: GNUNET_break (0); return 1; } - return 0; + ret = TALER_TESTING_main (argv, + "INFO", + CONFIG_FILE, + "exchange-account-exchange", + TALER_TESTING_BS_FAKEBANK, + &cred, + &run, + NULL); + GNUNET_OS_process_kill (anastasisd, + SIGTERM); + GNUNET_OS_process_wait (anastasisd); + GNUNET_OS_process_destroy (anastasisd); + GNUNET_free (anastasis_url); + return ret; } diff --git a/src/testing/test_anastasis_api.c b/src/testing/test_anastasis_api.c index 2767264..7d7e2ac 100644 --- a/src/testing/test_anastasis_api.c +++ b/src/testing/test_anastasis_api.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -52,34 +52,29 @@ #define MERCHANT_ACCOUNT_NAME "3" /** - * Configuration of the bank. + * Test credentials. */ -static struct TALER_TESTING_BankConfiguration bc; - -/** - * Configuration of the exchange. - */ -static struct TALER_TESTING_ExchangeConfiguration ec; +static struct TALER_TESTING_Credentials cred; /** * Payto URI of the customer (payer). */ -static char *payer_payto; +static const char *payer_payto; /** * Payto URI of the exchange (escrow account). */ -static char *exchange_payto; +static const char *exchange_payto; /** * Payto URI of the merchant (receiver). */ -static char *merchant_payto; +static const char *merchant_payto; /** * Merchant base URL. */ -static char *merchant_url; +static const char *merchant_url; /** * Anastasis base URL. @@ -87,11 +82,6 @@ static char *merchant_url; static char *anastasis_url; /** - * Merchant process. - */ -static struct GNUNET_OS_Process *merchantd; - -/** * Anastasis process. */ static struct GNUNET_OS_Process *anastasisd; @@ -129,7 +119,7 @@ cmd_transfer_to_exchange (const char *label, { return TALER_TESTING_cmd_admin_add_incoming (label, amount, - &bc.exchange_auth, + &cred.ba, payer_payto); } @@ -150,10 +140,12 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", "EUR:5", + 0, /* age */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", "create-reserve-1", "EUR:5", + 0, /* age */ MHD_HTTP_OK), TALER_TESTING_cmd_status ("withdraw-status-1", "create-reserve-1", @@ -207,14 +199,14 @@ run (void *cls, "The-Answer", ANASTASIS_TESTING_TSO_NONE, MHD_HTTP_NO_CONTENT), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + ANASTASIS_TESTING_cmd_truth_solve ( "keyshare-lookup-1", anastasis_url, "The-Answer", NULL, /* payment ref */ "truth-store-1", 0, - ANASTASIS_KSD_SUCCESS), + MHD_HTTP_OK), ANASTASIS_TESTING_cmd_truth_store ( "truth-store-2", anastasis_url, @@ -225,22 +217,20 @@ run (void *cls, file_secret, ANASTASIS_TESTING_TSO_NONE, MHD_HTTP_NO_CONTENT), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + ANASTASIS_TESTING_cmd_truth_solve ( "challenge-fail-1", anastasis_url, "Wrong-Answer", - NULL, - "truth-store-1", - 0, - ANASTASIS_KSD_INVALID_ANSWER), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + NULL, /* payment ref */ + "truth-store-1", /* upload ref */ + 0, /* security question mode */ + MHD_HTTP_FORBIDDEN), + ANASTASIS_TESTING_cmd_truth_challenge ( "file-challenge-run-1", anastasis_url, - NULL, /* no answer */ NULL, /* payment ref */ "truth-store-2", /* upload ref */ - 0, - ANASTASIS_KSD_PAYMENT_REQUIRED), + MHD_HTTP_PAYMENT_REQUIRED), /* what would we have to pay? */ TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal-2", merchant_url, @@ -257,48 +247,48 @@ run (void *cls, "EUR:1", NULL), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + ANASTASIS_TESTING_cmd_truth_challenge ( "file-challenge-run-2", anastasis_url, - NULL, /* no answer */ "file-challenge-run-1", /* payment ref */ "truth-store-2", - 0, - ANASTASIS_KSD_INVALID_ANSWER), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + MHD_HTTP_OK), + ANASTASIS_TESTING_cmd_truth_solve ( "file-challenge-run-3", anastasis_url, "file-challenge-run-2", /* answer */ "file-challenge-run-1", /* payment ref */ "truth-store-2", 1, - ANASTASIS_KSD_SUCCESS), + MHD_HTTP_OK), TALER_TESTING_cmd_end () }; struct TALER_TESTING_Command commands[] = { /* general setup */ - TALER_TESTING_cmd_auditor_add ("add-auditor-OK", - MHD_HTTP_NO_CONTENT, - false), - TALER_TESTING_cmd_wire_add ("add-wire-account", - "payto://x-taler-bank/localhost/2", - MHD_HTTP_NO_CONTENT, - false), - TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys", - CONFIG_FILE), - TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees", - CONFIG_FILE, - "EUR:0.01", - "EUR:0.01"), - TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys", - 1), + TALER_TESTING_cmd_run_fakebank ("run-fakebank", + cred.cfg, + "exchange-account-exchange"), + TALER_TESTING_cmd_system_start ("start-taler", + CONFIG_FILE, + "-em", + "-u", "exchange-account-exchange", + NULL), + TALER_TESTING_cmd_get_exchange ("get-exchange", + cred.cfg, + NULL, + true, + true), TALER_TESTING_cmd_merchant_post_instances ("instance-create-default", merchant_url, "default", - merchant_payto, - "EUR", MHD_HTTP_NO_CONTENT), + TALER_TESTING_cmd_merchant_post_account ( + "instance-create-default-account", + merchant_url, + merchant_payto, + NULL, NULL, + MHD_HTTP_OK), ANASTASIS_TESTING_cmd_config ("salt-request-1", anastasis_url, MHD_HTTP_OK), @@ -311,9 +301,8 @@ run (void *cls, TALER_TESTING_cmd_end () }; - TALER_TESTING_run_with_fakebank (is, - commands, - bc.exchange_auth.wire_gateway_url); + TALER_TESTING_run (is, + commands); } @@ -323,17 +312,6 @@ main (int argc, { int ret; - /* These environment variables get in the way... */ - unsetenv ("XDG_DATA_HOME"); - unsetenv ("XDG_CONFIG_HOME"); - GNUNET_log_setup ("test-anastasis-api", - "DEBUG", - NULL); - if (GNUNET_OK != - TALER_TESTING_prepare_fakebank (CONFIG_FILE, - "exchange-account-exchange", - &bc)) - return 77; { char dir[] = "/tmp/test-anastasis-file-XXXXXX"; @@ -348,73 +326,41 @@ main (int argc, "%s/.secret", dir); } - payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME); - exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME); - merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME); - if (NULL == - (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE))) - return 77; - TALER_TESTING_cleanup_files (CONFIG_FILE); + payer_payto = + "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME + "?receiver-name=62"; + exchange_payto = + "payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME + "?receiver-name=exchange"; + merchant_payto = + "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME + "?receiver-name=merchant"; + merchant_url = "http://localhost:8080/"; if (NULL == (anastasis_url = ANASTASIS_TESTING_prepare_anastasis (CONFIG_FILE))) return 77; - TALER_TESTING_cleanup_files (CONFIG_FILE); - - switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, - GNUNET_YES, - &ec)) + if (NULL == (anastasisd = + ANASTASIS_TESTING_run_anastasis (CONFIG_FILE, + anastasis_url))) { - case GNUNET_SYSERR: - GNUNET_break (0); - return 1; - case GNUNET_NO: - return 77; - case GNUNET_OK: - if (NULL == (merchantd = - TALER_TESTING_run_merchant (CONFIG_FILE, - merchant_url))) - { - GNUNET_break (0); - return 1; - } - if (NULL == (anastasisd = - ANASTASIS_TESTING_run_anastasis (CONFIG_FILE, - anastasis_url))) - { - GNUNET_break (0); - GNUNET_OS_process_kill (merchantd, - SIGTERM); - GNUNET_OS_process_wait (merchantd); - GNUNET_OS_process_destroy (merchantd); - return 1; - } - ret = TALER_TESTING_setup_with_exchange (&run, - NULL, - CONFIG_FILE); - GNUNET_OS_process_kill (merchantd, - SIGTERM); - GNUNET_OS_process_kill (anastasisd, - SIGTERM); - GNUNET_OS_process_wait (merchantd); - GNUNET_OS_process_wait (anastasisd); - GNUNET_OS_process_destroy (merchantd); - GNUNET_OS_process_destroy (anastasisd); - GNUNET_free (merchant_url); - GNUNET_free (anastasis_url); - - if (GNUNET_OK != ret) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Test failed in interpreter\n"); - return 1; - } - break; - default: GNUNET_break (0); return 1; } - return 0; + ret = TALER_TESTING_main (argv, + "INFO", + CONFIG_FILE, + "exchange-account-exchange", + TALER_TESTING_BS_FAKEBANK, + &cred, + &run, + NULL); + GNUNET_OS_process_kill (anastasisd, + SIGTERM); + GNUNET_OS_process_wait (anastasisd); + GNUNET_OS_process_destroy (anastasisd); + GNUNET_free (anastasis_url); + return ret; } diff --git a/src/testing/test_anastasis_api.conf b/src/testing/test_anastasis_api.conf index 8befd99..53801d4 100644 --- a/src/testing/test_anastasis_api.conf +++ b/src/testing/test_anastasis_api.conf @@ -1,231 +1,125 @@ # This file is in the public domain. # [PATHS] -# Persistent data storage for the testcase TALER_TEST_HOME = test_anastasis_api_home/ -TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/ - -# Persistent data storage -TALER_DATA_HOME = $TALER_TEST_HOME/.local/share/taler/ - -# Configuration files -TALER_CONFIG_HOME = $TALER_TEST_HOME/.config/taler/ - -# Cached data, no big deal if lost -TALER_CACHE_HOME = $TALER_TEST_HOME/.cache/taler/ +TALER_HOME = ${TALER_TEST_HOME:-${HOME:-${USERPROFILE}}} +TALER_DATA_HOME = ${TALER_TEST_HOME:-${XDG_DATA_HOME:-${TALER_HOME}/.local/share/}/.local/share/}taler/ +TALER_CONFIG_HOME = ${TALER_TEST_HOME:-${XDG_CONFIG_HOME:-${TALER_HOME}/.config/}/.config/}taler/ +TALER_CACHE_HOME = ${TALER_TEST_HOME:-${XDG_CACHE_HOME:-${TALER_HOME}/.cache/}/.cache/}taler/ +TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/ [taler] -# What currency do we use? -#currency = EUR -currency = EUR -#CURRENCY_ROUND_UNIT = EUR:0.01 -#CURRENCY_ROUND_UNIT = EUR:0.01 +CURRENCY = EUR +CURRENCY_ROUND_UNIT = EUR:0.01 [taler-helper-crypto-rsa] -# Reduce from 1 year to speed up test LOOKAHEAD_SIGN = 12 days [taler-helper-crypto-eddsa] -# Reduce from 1 year to speed up test LOOKAHEAD_SIGN = 12 days -# Reduce from 12 weeks to ensure we have multiple DURATION = 7 days - [bank] HTTP_PORT = 8082 -#BASE_URL = https://bank.test.taler.net/ +BASE_URL = http://localhost:8082/ -########################################## -# Configuration for Anastasis # -########################################## +[libeufin-bank] +CURRENCY = EUR +WIRE_TYPE = iban +IBAN_PAYTO_BIC = SANDBOXX +DEFAULT_CUSTOMER_DEBT_LIMIT = EUR:200 +DEFAULT_ADMIN_DEBT_LIMIT = EUR:2000 +REGISTRATION_BONUS_ENABLED = yes +REGISTRATION_BONUS = EUR:100 +SUGGESTED_WITHDRAWAL_EXCHANGE = http://localhost:8081/ +SERVE = tcp +PORT = 8082 [anastasis] PORT = 8086 - DB = postgres - BUSINESS_NAME = "Checker's Test Inc." - -# Upload limit UPLOAD_LIMIT_MB = 1 - ANNUAL_POLICY_UPLOAD_LIMIT = 64 - INSURANCE = EUR:0 - -SERVER_SALT = salty - - -# Annual fee we charge. -#ANNUAL_FEE = EUR:4.99 +PROVIDER_SALT = salty ANNUAL_FEE = EUR:4.99 - TRUTH_UPLOAD_FEE = EUR:0.0 - -# Base URL of anastasis. -# BASE_URL = http://localhost:8086/ +BASE_URL = http://localhost:8086/ [anastasis-merchant-backend] -# Where does our payment backend run? Must match PORT under [merchant] PAYMENT_BACKEND_URL = http://localhost:8080/ -# Authentication costs [authorization-question] -# Cost of authentication by question COST = EUR:0 [authorization-file] -# Cost of authentication by file (only for testing purposes) COST = EUR:1 [authorization-email] -# Cost of authentication by E-Mail COST = EUR:0 [authorization-sms] -# Cost of authentication by SMS COST = EUR:0 - -# Command which is executed for the sms authentication COMMAND = ./sms_authentication.sh - - - -# This specifies which database the postgres backend uses. [stasis-postgres] CONFIG = postgres:///anastasischeck -########################################## -# Configuration for the merchant backend # -########################################## - [merchant] - -# Which port do we run the backend on? (HTTP server) PORT = 8080 - -# How quickly do we want the exchange to send us our money? -# Used only if the frontend does not specify a value. WIRE_TRANSFER_DELAY = 0 s - -# Which plugin (backend) do we use for the DB. DB = postgres -# Default choice for maximum wire fee. -DEFAULT_MAX_WIRE_FEE = EUR:0.10 - -# Default choice for maximum deposit fee. -DEFAULT_MAX_DEPOSIT_FEE = EUR:0.10 - - -# This specifies which database the postgres backend uses. [merchantdb-postgres] CONFIG = postgres:///talercheck -# Sections starting with "exchange-" specify trusted exchanges -# (by the merchant) [merchant-exchange-default] MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG EXCHANGE_BASE_URL = http://localhost:8081/ -#MASTER_KEY = DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG -#EXCHANGE_BASE_URL = https://exchange.test.taler.net/ -#CURRENCY = EUR CURRENCY = EUR -# only fixes skips. [auditor] -BASE_URL = http://the.auditor/ -#BASE_URL = https://auditor.test.taler.net/ -#AUDITOR_KEY = DSDASDXAMDAARMNAD53ZA4AFAHA2QADAMAHHASWDAWXN84SDAA11 -# If currency does not match [TALER] section, the auditor -# will be ignored! -CURRENCY = EUR - -# Where do we store the auditor's private key? -AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv - -# Auditors must be in sections "auditor-", the rest of the section -# name could be anything. -[auditor-ezb] -# Informal name of the auditor. Just for the user. -NAME = European Central Bank - -# URL of the auditor (especially for in the future, when the -# auditor offers an automated issue reporting system). -# Not really used today. -URL = http://taler.ezb.eu/ - -# This is the important bit: the signing key of the auditor. -PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60 - -# Which currency is this auditor trusted for? -CURRENCY = EUR - - -################################################### -# Configuration for the exchange for the testcase # -################################################### +PORT = 8083 +BASE_URL = "http://localhost:8083/" [exchange] -# How to access our database +AML_THRESHOLD = EUR:1000000 DB = postgres - -# HTTP port the exchange listens to PORT = 8081 - -# how long are the signatures with the signkey valid? SIGNKEY_LEGAL_DURATION = 2 years - -# Our public key MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG - -# Base URL of the exchange. BASE_URL = "http://localhost:8081/" -#BASE_URL = https://exchange.test.taler.net/ - -# Network configuration for the normal API/service HTTP server -# serve via tcp socket (on PORT) SERVE = tcp +STEFAN_ABS = "EUR:5" [exchange-offline] - -# Where do we store the offline master private key of the exchange? MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv -# Where do we store the TOFU key material? SECM_TOFU_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/secm_tofus.pub - [taler-exchange-secmod-eddsa] -# Where do we store the generated private keys. KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-eddsa/keys [taler-exchange-secmod-rsa] -# Where do we store the generated private keys. KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-rsa/keys +[taler-exchange-secmod-cs] +KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-cs/keys -[exchangedb-postgres] -CONFIG = "postgres:///talercheck" -[auditordb-postgres] +[exchangedb-postgres] CONFIG = "postgres:///talercheck" -# Account of the EXCHANGE [exchange-account-exchange] -# What is the exchange's bank account (with the "Taler Bank" demo system)? -PAYTO_URI = "payto://x-taler-bank/localhost:8082/2" +PAYTO_URI = "payto://x-taler-bank/localhost:8082/2?receiver-name=exchange" ENABLE_DEBIT = YES ENABLE_CREDIT = YES [exchange-accountcredentials-exchange] -WIRE_GATEWAY_URL = "http://localhost:8082/2/" +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" WIRE_GATEWAY_AUTH_METHOD = NONE - - [coin_eur_ct_1] value = EUR:0.01 duration_withdraw = 7 days @@ -236,6 +130,7 @@ fee_deposit = EUR:0.00 fee_refresh = EUR:0.01 fee_refund = EUR:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_eur_ct_10] value = EUR:0.10 @@ -247,6 +142,7 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_eur_1] value = EUR:1 @@ -258,6 +154,7 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 +CIPHER = RSA [coin_eur_5] value = EUR:5 @@ -269,3 +166,4 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 +CIPHER = RSA diff --git a/src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json b/src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json deleted file mode 100644 index f798275..0000000 --- a/src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "payto_uri": "payto://x-taler-bank/localhost:8082/2", - "master_sig": "AM32QB4RYMWK548PE63PJXJMWSA001TFFWTZZPSSD8HQ8JE4D5V5X8WTSYSX59ANF4YRTRMF5Q4Q12CE2KTA8KQ03CM11YDTK75SJ20"} diff --git a/src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_anastasis_api_home/taler/exchange-offline/master.priv index c20942d..c20942d 100644 --- a/src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv +++ b/src/testing/test_anastasis_api_home/taler/exchange-offline/master.priv diff --git a/src/testing/test_anastasis_api_home/taler/exchange/offline-keys/master.priv b/src/testing/test_anastasis_api_home/taler/exchange/offline-keys/master.priv new file mode 100644 index 0000000..c20942d --- /dev/null +++ b/src/testing/test_anastasis_api_home/taler/exchange/offline-keys/master.priv @@ -0,0 +1 @@ +åÊk;d³_Uû}£A.wÔ"!Gûçv_m "_ò
\ No newline at end of file diff --git a/src/testing/testing_api_cmd_config.c b/src/testing/testing_api_cmd_config.c index 58a58dc..542e140 100644 --- a/src/testing/testing_api_cmd_config.c +++ b/src/testing/testing_api_cmd_config.c @@ -3,14 +3,14 @@ Copyright (C) 2019, 2021 Anastasis SARL Anastasis 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 + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ /** @@ -53,7 +53,7 @@ struct ConfigState /** * The salt value from server. */ - struct ANASTASIS_CRYPTO_ProviderSaltP salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; }; @@ -61,39 +61,26 @@ struct ConfigState * Function called with the results of a #ANASTASIS_get_config(). * * @param cls closure - * @param http_status HTTP status of the request * @param config config from the server */ static void config_cb (void *cls, - unsigned int http_status, const struct ANASTASIS_Config *config) { struct ConfigState *ss = cls; ss->so = NULL; - if (http_status != ss->http_status) + if (config->http_status != ss->http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - http_status, - ss->is->commands[ss->is->ip].label, - __FILE__, - __LINE__); - TALER_TESTING_interpreter_fail (ss->is); + TALER_TESTING_unexpected_status (ss->is, + config->http_status, + ss->http_status); return; } - if (NULL == config) + if (GNUNET_OK == config->http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Config is NULL, command %s in %s:%u\n", - ss->is->commands[ss->is->ip].label, - __FILE__, - __LINE__); - TALER_TESTING_interpreter_fail (ss->is); - return; + ss->provider_salt = config->details.ok.provider_salt; } - ss->salt = config->salt; TALER_TESTING_interpreter_next (ss->is); } @@ -113,10 +100,11 @@ config_run (void *cls, struct ConfigState *ss = cls; ss->is = is; - ss->so = ANASTASIS_get_config (is->ctx, - ss->anastasis_url, - &config_cb, - ss); + ss->so = ANASTASIS_get_config ( + TALER_TESTING_interpreter_get_context (is), + ss->anastasis_url, + &config_cb, + ss); if (NULL == ss->so) { GNUNET_break (0); @@ -167,10 +155,8 @@ config_traits (void *cls, unsigned int index) { struct ConfigState *ss = cls; - struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_salt (0, - &ss->salt), + ANASTASIS_TESTING_make_trait_provider_salt (&ss->provider_salt), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_api_cmd_policy_lookup.c b/src/testing/testing_api_cmd_policy_lookup.c index eaff0f2..2d854c5 100644 --- a/src/testing/testing_api_cmd_policy_lookup.c +++ b/src/testing/testing_api_cmd_policy_lookup.c @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -72,32 +72,26 @@ struct PolicyLookupState * Function called with the results of a #ANASTASIS_policy_lookup(). * * @param cls closure - * @param http_status HTTP status of the request * @param dd details about the lookup operation */ static void policy_lookup_cb (void *cls, - unsigned int http_status, const struct ANASTASIS_DownloadDetails *dd) { struct PolicyLookupState *pls = cls; pls->plo = NULL; - if (http_status != pls->http_status) + if (dd->http_status != pls->http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - http_status, - pls->is->commands[pls->is->ip].label, - __FILE__, - __LINE__); - TALER_TESTING_interpreter_fail (pls->is); + TALER_TESTING_unexpected_status (pls->is, + dd->http_status, + pls->http_status); return; } if (NULL != pls->upload_reference) { - if ( (MHD_HTTP_OK == http_status) && - (0 != GNUNET_memcmp (&dd->curr_policy_hash, + if ( (MHD_HTTP_OK == dd->http_status) && + (0 != GNUNET_memcmp (&dd->details.ok.curr_policy_hash, pls->upload_hash)) ) { GNUNET_break (0); @@ -140,7 +134,6 @@ policy_lookup_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_hash (upload_cmd, - ANASTASIS_TESTING_TRAIT_HASH_CURRENT, &pls->upload_hash)) { GNUNET_break (0); @@ -149,7 +142,6 @@ policy_lookup_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_account_pub (upload_cmd, - 0, &anastasis_pub)) { GNUNET_break (0); @@ -158,11 +150,12 @@ policy_lookup_run (void *cls, } pls->anastasis_pub = *anastasis_pub; } - pls->plo = ANASTASIS_policy_lookup (is->ctx, - pls->anastasis_url, - &pls->anastasis_pub, - &policy_lookup_cb, - pls); + pls->plo = ANASTASIS_policy_lookup ( + TALER_TESTING_interpreter_get_context (is), + pls->anastasis_url, + &pls->anastasis_pub, + &policy_lookup_cb, + pls); if (NULL == pls->plo) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_policy_store.c b/src/testing/testing_api_cmd_policy_store.c index 4b96472..edc753d 100644 --- a/src/testing/testing_api_cmd_policy_store.c +++ b/src/testing/testing_api_cmd_policy_store.c @@ -3,16 +3,16 @@ Copyright (C) 2014-2019 Anastasis SARL ANASTASIS is free software; you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as + 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. ANASTASIS 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. + GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public + You should have received a copy of the GNU General Public License along with ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ @@ -139,13 +139,9 @@ policy_store_cb (void *cls, pss->pso = NULL; if (ud->http_status != pss->http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - ud->http_status, - pss->is->commands[pss->is->ip].label, - __FILE__, - __LINE__); - TALER_TESTING_interpreter_fail (pss->is); + TALER_TESTING_unexpected_status (pss->is, + ud->http_status, + pss->http_status); return; } switch (ud->us) @@ -229,7 +225,6 @@ policy_store_run (void *cls, if (GNUNET_OK != ANASTASIS_TESTING_get_trait_account_priv (ref, - 0, &priv)) { GNUNET_break (0); @@ -243,7 +238,6 @@ policy_store_run (void *cls, if (GNUNET_OK != ANASTASIS_TESTING_get_trait_account_pub (ref, - 0, &pub)) { GNUNET_break (0); @@ -257,7 +251,6 @@ policy_store_run (void *cls, if (GNUNET_OK != ANASTASIS_TESTING_get_trait_payment_secret (ref, - 0, &ps)) { GNUNET_break (0); @@ -279,11 +272,12 @@ policy_store_run (void *cls, pss->recovery_data_size, &pss->curr_hash); pss->pso = ANASTASIS_policy_store ( - is->ctx, + TALER_TESTING_interpreter_get_context (is), pss->anastasis_url, &pss->anastasis_priv, pss->recovery_data, pss->recovery_data_size, + "metadata", strlen ("metadata"), (0 != (ANASTASIS_TESTING_PSO_REQUEST_PAYMENT & pss->psopt)), pss->payment_secret_set ? &pss->payment_secret_request : NULL, GNUNET_TIME_UNIT_ZERO, @@ -341,18 +335,12 @@ policy_store_traits (void *cls, { struct PolicyStoreState *pss = cls; struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_claim_token (0, - &pss->claim_token), - TALER_TESTING_make_trait_order_id (0, - pss->order_id), - ANASTASIS_TESTING_make_trait_hash (0, - &pss->curr_hash), - ANASTASIS_TESTING_make_trait_account_pub (0, - &pss->anastasis_pub), - ANASTASIS_TESTING_make_trait_account_priv (0, - &pss->anastasis_priv), - ANASTASIS_TESTING_make_trait_payment_secret (0, - &pss->payment_secret_response), + TALER_TESTING_make_trait_claim_token (&pss->claim_token), + TALER_TESTING_make_trait_order_id (pss->order_id), + ANASTASIS_TESTING_make_trait_hash (&pss->curr_hash), + ANASTASIS_TESTING_make_trait_account_pub (&pss->anastasis_pub), + ANASTASIS_TESTING_make_trait_account_priv (&pss->anastasis_priv), + ANASTASIS_TESTING_make_trait_payment_secret (&pss->payment_secret_response), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_api_cmd_truth_challenge.c b/src/testing/testing_api_cmd_truth_challenge.c new file mode 100644 index 0000000..c399345 --- /dev/null +++ b/src/testing/testing_api_cmd_truth_challenge.c @@ -0,0 +1,368 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2022 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_cmd_truth_challenge.c + * @brief Testing of Implementation of the /truth GET + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ + +#include "platform.h" +#include "anastasis_testing_lib.h" +#include <taler/taler_util.h> +#include <taler/taler_testing_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * State for a "keyshare lookup" CMD. + */ +struct TruthChallengeState +{ + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * URL of the anastasis backend. + */ + const char *anastasis_url; + + /** + * Expected HTTP status code. + */ + unsigned int expected_http_status; + + /** + * The /truth GET operation handle. + */ + struct ANASTASIS_TruthChallengeOperation *tco; + + /** + * Reference to upload command we expect to lookup. + */ + const char *upload_reference; + + /** + * Reference to upload command we expect to lookup. + */ + const char *payment_reference; + + /** + * Payment secret requested by the service, if any. + */ + struct ANASTASIS_PaymentSecretP payment_secret_response; + + /** + * Taler-URI with payment request, if any. + */ + char *pay_uri; + + /** + * Order ID for payment request, if any. + */ + char *order_id; + + /** + * "code" returned by service, if any. + */ + char *code; + + /** + * "instructions" for how to solve the challenge as returned by service, if any. + */ + char *instructions; + +}; + + +static void +truth_challenge_cb (void *cls, + const struct ANASTASIS_TruthChallengeDetails *tcd) +{ + struct TruthChallengeState *ksls = cls; + + ksls->tco = NULL; + if (tcd->http_status != ksls->expected_http_status) + { + TALER_TESTING_unexpected_status (ksls->is, + tcd->http_status, + ksls->expected_http_status); + return; + } + switch (tcd->http_status) + { + case MHD_HTTP_OK: + switch (tcd->details.success.cs) + { + case ANASTASIS_CS_FILE_WRITTEN: + { + FILE *file; + char code[22]; + + file = fopen (tcd->details.success.details.challenge_filename, + "r"); + if (NULL == file) + { + GNUNET_log_strerror_file ( + GNUNET_ERROR_TYPE_ERROR, + "open", + tcd->details.success.details.challenge_filename); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (0 == fscanf (file, + "%21s", + code)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "fscanf", + tcd->details.success.details. + challenge_filename); + GNUNET_break (0 == fclose (file)); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + GNUNET_break (0 == fclose (file)); + ksls->code = GNUNET_strdup (code); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read code `%s'\n", + code); + } + break; + case ANASTASIS_CS_TAN_SENT: + ksls->instructions = GNUNET_strdup ( + tcd->details.success.details.tan_address_hint); + break; + case ANASTASIS_CS_TAN_ALREADY_SENT: + break; + case ANASTASIS_CS_WIRE_FUNDS: + /* FIXME: not implemented */ + GNUNET_break (0); + return; + } + break; + case MHD_HTTP_PAYMENT_REQUIRED: + ksls->pay_uri = GNUNET_strdup ( + tcd->details.payment_required.payment_request); + ksls->payment_secret_response = tcd->details.payment_required.ps; + { + struct TALER_MERCHANT_PayUriData pd; + + if (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (ksls->pay_uri, + &pd)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + ksls->order_id = GNUNET_strdup (pd.order_id); + TALER_MERCHANT_parse_pay_uri_free (&pd); + } + + break; + default: + break; + } + TALER_TESTING_interpreter_next (ksls->is); +} + + +static void +truth_challenge_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct TruthChallengeState *ksls = cls; + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key; + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid; + const struct ANASTASIS_PaymentSecretP *payment_secret; + + ksls->is = is; + if (NULL == ksls->upload_reference) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + { + const struct TALER_TESTING_Command *upload_cmd; + + upload_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + ksls->upload_reference); + if (NULL == upload_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (GNUNET_OK != + ANASTASIS_TESTING_get_trait_truth_uuid (upload_cmd, + &truth_uuid)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (NULL == truth_uuid) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (GNUNET_OK != + ANASTASIS_TESTING_get_trait_truth_key (upload_cmd, + &truth_key)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (NULL == truth_key) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + } + + if (NULL != ksls->payment_reference) + { + const struct TALER_TESTING_Command *payment_cmd; + + payment_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + ksls->payment_reference); + if (GNUNET_OK != + ANASTASIS_TESTING_get_trait_payment_secret (payment_cmd, + &payment_secret)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + } + else + { + payment_secret = NULL; + } + + ksls->tco = ANASTASIS_truth_challenge ( + TALER_TESTING_interpreter_get_context (is), + ksls->anastasis_url, + truth_uuid, + truth_key, + payment_secret, + &truth_challenge_cb, + ksls); + if (NULL == ksls->tco) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } +} + + +static void +truth_challenge_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct TruthChallengeState *ksls = cls; + + if (NULL != ksls->tco) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command '%s' did not complete (keyshare lookup)\n", + cmd->label); + ANASTASIS_truth_challenge_cancel (ksls->tco); + ksls->tco = NULL; + } + GNUNET_free (ksls->pay_uri); + GNUNET_free (ksls->order_id); + GNUNET_free (ksls->code); + GNUNET_free (ksls->instructions); + GNUNET_free (ksls); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure + * @param[out] ret result (could be anything) + * @param[out] trait name of the trait + * @param index index number of the object to extract. + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +truth_challenge_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct TruthChallengeState *ksls = cls; + struct TALER_TESTING_Trait traits[] = { + ANASTASIS_TESTING_make_trait_payment_secret ( + &ksls->payment_secret_response), + TALER_TESTING_make_trait_payto_uri (ksls->pay_uri), + TALER_TESTING_make_trait_order_id (ksls->order_id), + ANASTASIS_TESTING_make_trait_code (ksls->code), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +struct TALER_TESTING_Command +ANASTASIS_TESTING_cmd_truth_challenge ( + const char *label, + const char *anastasis_url, + const char *payment_ref, + const char *upload_ref, + unsigned int http_status) +{ + struct TruthChallengeState *ksls; + + GNUNET_assert (NULL != upload_ref); + ksls = GNUNET_new (struct TruthChallengeState); + ksls->expected_http_status = http_status; + ksls->anastasis_url = anastasis_url; + ksls->upload_reference = upload_ref; + ksls->payment_reference = payment_ref; + { + struct TALER_TESTING_Command cmd = { + .cls = ksls, + .label = label, + .run = &truth_challenge_run, + .cleanup = &truth_challenge_cleanup, + .traits = &truth_challenge_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_truth_challenge.c */ diff --git a/src/testing/testing_api_cmd_keyshare_lookup.c b/src/testing/testing_api_cmd_truth_solve.c index 04ecf43..29157ed 100644 --- a/src/testing/testing_api_cmd_keyshare_lookup.c +++ b/src/testing/testing_api_cmd_truth_solve.c @@ -1,20 +1,20 @@ /* This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL + Copyright (C) 2020, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** - * @file testing/testing_api_cmd_keyshare_lookup.c + * @file testing/testing_api_cmd_truth_solve.c * @brief Testing of Implementation of the /truth GET * @author Christian Grothoff * @author Dennis Neufeld @@ -31,7 +31,7 @@ /** * State for a "keyshare lookup" CMD. */ -struct KeyShareLookupState +struct TruthSolveState { /** * The interpreter state. @@ -46,12 +46,18 @@ struct KeyShareLookupState /** * Expected status code. */ - enum ANASTASIS_KeyShareDownloadStatus expected_ksdd; + unsigned int expected_http_status; + + /** + * Resulting encrypted key share. + * Note: currently not used. + */ + struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks; /** * The /truth GET operation handle. */ - struct ANASTASIS_KeyShareLookupOperation *kslo; + struct ANASTASIS_TruthSolveOperation *tso; /** * answer to a challenge @@ -113,100 +119,31 @@ struct KeyShareLookupState static void -keyshare_lookup_cb (void *cls, - const struct ANASTASIS_KeyShareDownloadDetails *dd) +truth_solve_cb (void *cls, + const struct ANASTASIS_TruthSolveReply *tsr) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; - ksls->kslo = NULL; - if (dd->status != ksls->expected_ksdd) + ksls->tso = NULL; + if (tsr->http_status != ksls->expected_http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - dd->status, - ksls->is->commands[ksls->is->ip].label, - __FILE__, - __LINE__); - TALER_TESTING_interpreter_fail (ksls->is); + TALER_TESTING_unexpected_status (ksls->is, + tsr->http_status, + ksls->expected_http_status); return; } - switch (dd->status) + switch (tsr->http_status) { - case ANASTASIS_KSD_SUCCESS: - break; - case ANASTASIS_KSD_PAYMENT_REQUIRED: - ksls->pay_uri = GNUNET_strdup (dd->details.payment_required.taler_pay_uri); - ksls->payment_secret_response = dd->details.payment_required.payment_secret; - { - struct TALER_MERCHANT_PayUriData pd; - - if (GNUNET_OK != - TALER_MERCHANT_parse_pay_uri (ksls->pay_uri, - &pd)) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (ksls->is); - return; - } - ksls->order_id = GNUNET_strdup (pd.order_id); - TALER_MERCHANT_parse_pay_uri_free (&pd); - } - - break; - case ANASTASIS_KSD_INVALID_ANSWER: - if (ksls->filename) - { - FILE *file; - char code[22]; - - file = fopen (ksls->filename, - "r"); - if (NULL == file) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "open", - ksls->filename); - TALER_TESTING_interpreter_fail (ksls->is); - return; - } - if (0 == fscanf (file, - "%21s", - code)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "fscanf", - ksls->filename); - GNUNET_break (0 == fclose (file)); - TALER_TESTING_interpreter_fail (ksls->is); - return; - } - GNUNET_break (0 == fclose (file)); - ksls->code = GNUNET_strdup (code); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Read code `%s'\n", - code); - } - else - { - ksls->instructions = GNUNET_strndup ( - dd->details.open_challenge.body, - dd->details.open_challenge.body_size); - } - break; - case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION: - ksls->redirect_uri = GNUNET_strdup (dd->details.redirect_url); + case MHD_HTTP_OK: + ksls->eks = tsr->details.success.eks; break; - case ANASTASIS_KSD_SERVER_ERROR: + case MHD_HTTP_PAYMENT_REQUIRED: + ksls->pay_uri = GNUNET_strdup ( + tsr->details.payment_required.payment_request); + ksls->payment_secret_response = tsr->details.payment_required.ps; + ksls->order_id = GNUNET_strdup (tsr->details.payment_required.pd->order_id); break; - case ANASTASIS_KSD_CLIENT_FAILURE: - break; - case ANASTASIS_KSD_TRUTH_UNKNOWN: - break; - case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED: - break; - case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT: - break; - case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS: + default: break; } TALER_TESTING_interpreter_next (ksls->is); @@ -214,11 +151,11 @@ keyshare_lookup_cb (void *cls, static void -keyshare_lookup_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) +truth_solve_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key; const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid; const struct ANASTASIS_PaymentSecretP *payment_secret; @@ -247,9 +184,8 @@ keyshare_lookup_run (void *cls, const char *fn; if (GNUNET_OK != - TALER_TESTING_get_trait_string (upload_cmd, - 0, - &fn)) + ANASTASIS_TESTING_get_trait_filename (upload_cmd, + &fn)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (ksls->is); @@ -260,7 +196,6 @@ keyshare_lookup_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_truth_uuid (upload_cmd, - 0, &truth_uuid)) { GNUNET_break (0); @@ -275,7 +210,6 @@ keyshare_lookup_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_truth_key (upload_cmd, - 0, &truth_key)) { GNUNET_break (0); @@ -304,7 +238,6 @@ keyshare_lookup_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_code (download_cmd, - 0, &answer)) { GNUNET_break (0); @@ -323,17 +256,22 @@ keyshare_lookup_run (void *cls, /* answer is the answer */ answer = ksls->answer; } + if (NULL == answer) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } if (NULL != ksls->payment_reference) { const struct TALER_TESTING_Command *payment_cmd; - payment_cmd = TALER_TESTING_interpreter_lookup_command - (is, - ksls->payment_reference); + payment_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + ksls->payment_reference); if (GNUNET_OK != ANASTASIS_TESTING_get_trait_payment_secret (payment_cmd, - 0, &payment_secret)) { GNUNET_break (0); @@ -349,23 +287,21 @@ keyshare_lookup_run (void *cls, { struct GNUNET_HashCode h_answer; - if (NULL != answer) - GNUNET_CRYPTO_hash (answer, - strlen (answer), - &h_answer); - ksls->kslo = ANASTASIS_keyshare_lookup (is->ctx, - ksls->anastasis_url, - truth_uuid, - truth_key, - payment_secret, - GNUNET_TIME_UNIT_ZERO, - (NULL != answer) - ? &h_answer - : NULL, - &keyshare_lookup_cb, - ksls); + GNUNET_CRYPTO_hash (answer, + strlen (answer), + &h_answer); + ksls->tso = ANASTASIS_truth_solve ( + TALER_TESTING_interpreter_get_context (is), + ksls->anastasis_url, + truth_uuid, + truth_key, + payment_secret, + GNUNET_TIME_UNIT_ZERO, + &h_answer, + &truth_solve_cb, + ksls); } - if (NULL == ksls->kslo) + if (NULL == ksls->tso) { GNUNET_break (0); TALER_TESTING_interpreter_fail (ksls->is); @@ -375,22 +311,23 @@ keyshare_lookup_run (void *cls, static void -keyshare_lookup_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) +truth_solve_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; - if (NULL != ksls->kslo) + if (NULL != ksls->tso) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Command '%s' did not complete (keyshare lookup)\n", cmd->label); - ANASTASIS_keyshare_lookup_cancel (ksls->kslo); - ksls->kslo = NULL; + ANASTASIS_truth_solve_cancel (ksls->tso); + ksls->tso = NULL; } GNUNET_free (ksls->pay_uri); GNUNET_free (ksls->order_id); GNUNET_free (ksls->code); + GNUNET_free (ksls->filename); GNUNET_free (ksls->instructions); GNUNET_free (ksls->redirect_uri); GNUNET_free (ksls); @@ -406,22 +343,19 @@ keyshare_lookup_cleanup (void *cls, * @param index index number of the object to extract. * @return #GNUNET_OK on success */ -static int -keyshare_lookup_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) +static enum GNUNET_GenericReturnValue +truth_solve_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_payment_secret (0, - &ksls->payment_secret_response), - TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL, - ksls->pay_uri), - TALER_TESTING_make_trait_order_id (0, - ksls->order_id), - ANASTASIS_TESTING_make_trait_code (0, - ksls->code), + ANASTASIS_TESTING_make_trait_payment_secret ( + &ksls->payment_secret_response), + TALER_TESTING_make_trait_payto_uri (ksls->pay_uri), + TALER_TESTING_make_trait_order_id (ksls->order_id), + ANASTASIS_TESTING_make_trait_code (ksls->code), TALER_TESTING_trait_end () }; @@ -433,20 +367,20 @@ keyshare_lookup_traits (void *cls, struct TALER_TESTING_Command -ANASTASIS_TESTING_cmd_keyshare_lookup ( +ANASTASIS_TESTING_cmd_truth_solve ( const char *label, const char *anastasis_url, const char *answer, const char *payment_ref, const char *upload_ref, int lookup_mode, - enum ANASTASIS_KeyShareDownloadStatus ksdd) + unsigned int http_status) { - struct KeyShareLookupState *ksls; + struct TruthSolveState *ksls; GNUNET_assert (NULL != upload_ref); - ksls = GNUNET_new (struct KeyShareLookupState); - ksls->expected_ksdd = ksdd; + ksls = GNUNET_new (struct TruthSolveState); + ksls->expected_http_status = http_status; ksls->anastasis_url = anastasis_url; ksls->upload_reference = upload_ref; ksls->payment_reference = payment_ref; @@ -456,9 +390,9 @@ ANASTASIS_TESTING_cmd_keyshare_lookup ( struct TALER_TESTING_Command cmd = { .cls = ksls, .label = label, - .run = &keyshare_lookup_run, - .cleanup = &keyshare_lookup_cleanup, - .traits = &keyshare_lookup_traits + .run = &truth_solve_run, + .cleanup = &truth_solve_cleanup, + .traits = &truth_solve_traits }; return cmd; @@ -466,4 +400,4 @@ ANASTASIS_TESTING_cmd_keyshare_lookup ( } -/* end of testing_api_cmd_keyshare_lookup.c */ +/* end of testing_api_cmd_truth_solve.c */ diff --git a/src/testing/testing_api_cmd_truth_store.c b/src/testing/testing_api_cmd_truth_store.c index 141ef20..f7a6ece 100644 --- a/src/testing/testing_api_cmd_truth_store.c +++ b/src/testing/testing_api_cmd_truth_store.c @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -125,16 +125,11 @@ truth_store_cb (void *cls, struct TruthStoreState *tss = cls; tss->tso = NULL; - if ( (NULL == ud) || - (ud->http_status != tss->http_status) ) + if (ud->http_status != tss->http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - (NULL != ud) ? ud->http_status : 0, - tss->is->commands[tss->is->ip].label, - __FILE__, - __LINE__); - TALER_TESTING_interpreter_fail (tss->is); + TALER_TESTING_unexpected_status (tss->is, + ud->http_status, + tss->http_status); return; } switch (ud->us) @@ -205,7 +200,6 @@ truth_store_run (void *cls, if (GNUNET_OK != ANASTASIS_TESTING_get_trait_truth_uuid (ref, - 0, &uuid)) { GNUNET_break (0); @@ -215,7 +209,6 @@ truth_store_run (void *cls, tss->uuid = *uuid; if (GNUNET_OK != ANASTASIS_TESTING_get_trait_eks (ref, - 0, &eks)) { GNUNET_break (0); @@ -275,7 +268,7 @@ truth_store_run (void *cls, GNUNET_free (t); } tss->tso = ANASTASIS_truth_store ( - is->ctx, + TALER_TESTING_interpreter_get_context (is), tss->anastasis_url, &tss->uuid, tss->method, @@ -335,7 +328,7 @@ truth_store_cleanup (void *cls, * @param index index number of the object to extract. * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue truth_store_traits (void *cls, const void **ret, const char *trait, @@ -343,18 +336,12 @@ truth_store_traits (void *cls, { struct TruthStoreState *tss = cls; struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_truth_uuid (0, - &tss->uuid), - ANASTASIS_TESTING_make_trait_truth_key (0, - &tss->key), - ANASTASIS_TESTING_make_trait_eks (0, - &tss->encrypted_keyshare), - ANASTASIS_TESTING_make_trait_payment_secret (0, - &tss->payment_secret_response), - TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL, - tss->pay_uri), - TALER_TESTING_make_trait_string (0, - tss->filename), + ANASTASIS_TESTING_make_trait_truth_uuid (&tss->uuid), + ANASTASIS_TESTING_make_trait_truth_key (&tss->key), + ANASTASIS_TESTING_make_trait_eks (&tss->encrypted_keyshare), + ANASTASIS_TESTING_make_trait_payment_secret (&tss->payment_secret_response), + TALER_TESTING_make_trait_payto_uri (tss->pay_uri), + ANASTASIS_TESTING_make_trait_filename (tss->filename), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_api_helpers.c b/src/testing/testing_api_helpers.c index 15fa136..f131d00 100644 --- a/src/testing/testing_api_helpers.c +++ b/src/testing/testing_api_helpers.c @@ -3,16 +3,16 @@ Copyright (C) 2014-2021 Anastasis SARL ANASTASIS is free software; you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as + 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. ANASTASIS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of ANASTASISABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. + GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public + You should have received a copy of the GNU General Public License along with ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ diff --git a/src/testing/testing_api_trait_account_priv.c b/src/testing/testing_api_trait_account_priv.c deleted file mode 100644 index aa8addd..0000000 --- a/src/testing/testing_api_trait_account_priv.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Privlic License for more details. - - You should have received a copy of the GNU Affero General Privlic - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_account_priv.c - * @brief traits to offer a account_priv - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_ACCOUNT_PRIV "anastasis-account_priv" - - -int -ANASTASIS_TESTING_get_trait_account_priv ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPrivateKeyP **priv) -{ - return cmd->traits (cmd->cls, - (const void **) priv, - ANASTASIS_TESTING_TRAIT_ACCOUNT_PRIV, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_account_priv ( - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *priv) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_ACCOUNT_PRIV, - .ptr = (const void *) priv - }; - - return ret; -} - - -/* end of testing_api_trait_account_priv.c */ diff --git a/src/testing/testing_api_trait_account_pub.c b/src/testing/testing_api_trait_account_pub.c deleted file mode 100644 index b4bc6f5..0000000 --- a/src/testing/testing_api_trait_account_pub.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_account_pub.c - * @brief traits to offer a account_pub - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_ACCOUNT_PUB "anastasis-account_pub" - - -int -ANASTASIS_TESTING_get_trait_account_pub ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPublicKeyP **pub) -{ - return cmd->traits (cmd->cls, - (const void **) pub, - ANASTASIS_TESTING_TRAIT_ACCOUNT_PUB, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_account_pub ( - unsigned int index, - const struct ANASTASIS_CRYPTO_AccountPublicKeyP *h) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_ACCOUNT_PUB, - .ptr = (const void *) h - }; - - return ret; -} - - -/* end of testing_api_trait_account_pub.c */ diff --git a/src/testing/testing_api_trait_code.c b/src/testing/testing_api_trait_code.c deleted file mode 100644 index bdc289b..0000000 --- a/src/testing/testing_api_trait_code.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_code.c - * @brief traits to offers a code for a challenge - * @author Dominik Meister - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_CODE "anastasis-code" - - -int -ANASTASIS_TESTING_get_trait_code ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const char **code) -{ - return cmd->traits (cmd->cls, - (const void **) code, - ANASTASIS_TESTING_TRAIT_CODE, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_code ( - unsigned int index, - const char *code) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_CODE, - .ptr = (const void *) code - }; - - return ret; -} - - -/* end of testing_api_trait_code.c */ diff --git a/src/testing/testing_api_trait_eks.c b/src/testing/testing_api_trait_eks.c deleted file mode 100644 index d148456..0000000 --- a/src/testing/testing_api_trait_eks.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2021 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_eks.c - * @brief traits to offer a payment identifier - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_EKS \ - "anastasis-eks" - - -int -ANASTASIS_TESTING_get_trait_eks ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_EncryptedKeyShareP **eks) -{ - return cmd->traits (cmd->cls, - (const void **) eks, - ANASTASIS_TESTING_TRAIT_EKS, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_eks ( - unsigned int index, - const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *eks) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_EKS, - .ptr = (const void *) eks - }; - return ret; -} - - -/* end of testing_api_trait_eks.c */ diff --git a/src/testing/testing_api_trait_hash.c b/src/testing/testing_api_trait_hash.c deleted file mode 100644 index 9f9d554..0000000 --- a/src/testing/testing_api_trait_hash.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file anastasis/src/testing/testing_api_trait_hash.c - * @brief traits to offer a hash - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_HASH "anastasis-hash" - - -int -ANASTASIS_TESTING_get_trait_hash ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct GNUNET_HashCode **h) -{ - return cmd->traits (cmd->cls, - (const void **) h, - ANASTASIS_TESTING_TRAIT_HASH, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_hash ( - unsigned int index, - const struct GNUNET_HashCode *h) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_HASH, - .ptr = (const void *) h - }; - return ret; -} - - -/* end of testing_api_trait_hash.c */ diff --git a/src/testing/testing_api_trait_payment_secret.c b/src/testing/testing_api_trait_payment_secret.c deleted file mode 100644 index aa580da..0000000 --- a/src/testing/testing_api_trait_payment_secret.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_payment_secret.c - * @brief traits to offer a payment identifier - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_PAYMENT_SECRET \ - "anastasis-payment_secret" - - -int -ANASTASIS_TESTING_get_trait_payment_secret ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_PaymentSecretP **payment_secret) -{ - return cmd->traits (cmd->cls, - (const void **) payment_secret, - ANASTASIS_TESTING_TRAIT_PAYMENT_SECRET, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_payment_secret ( - unsigned int index, - const struct ANASTASIS_PaymentSecretP *h) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_PAYMENT_SECRET, - .ptr = (const void *) h - }; - - return ret; -} - - -/* end of testing_api_trait_payment_secret.c */ diff --git a/src/testing/testing_api_trait_salt.c b/src/testing/testing_api_trait_salt.c deleted file mode 100644 index 178b092..0000000 --- a/src/testing/testing_api_trait_salt.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_salt.c - * @brief traits to offer a hash - * @author Christian Grothoff - * @author Dominik Meister - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_SALT "anastasis-provider-salt" - - -int -ANASTASIS_TESTING_get_trait_salt ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_ProviderSaltP **s) -{ - return cmd->traits (cmd->cls, - (const void **) s, - ANASTASIS_TESTING_TRAIT_SALT, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_salt ( - unsigned int index, - const struct ANASTASIS_CRYPTO_ProviderSaltP *s) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_SALT, - .ptr = (const void *) s - }; - - return ret; -} - - -/* end of testing_api_trait_salt.c */ diff --git a/src/testing/testing_api_trait_truth_key.c b/src/testing/testing_api_trait_truth_key.c deleted file mode 100644 index 5de8860..0000000 --- a/src/testing/testing_api_trait_truth_key.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019, 2021 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_truth_key.c - * @brief traits to offer a payment identifier - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_TRUTH_KEY \ - "anastasis-truth_key" - - -int -ANASTASIS_TESTING_get_trait_truth_key - (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthKeyP **truth_key) -{ - return cmd->traits (cmd->cls, - (const void **) truth_key, - ANASTASIS_TESTING_TRAIT_TRUTH_KEY, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_truth_key - (unsigned int index, - const struct ANASTASIS_CRYPTO_TruthKeyP *h) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_TRUTH_KEY, - .ptr = (const void *) h - }; - return ret; -} - - -/* end of testing_api_trait_truth_key.c */ diff --git a/src/testing/testing_api_trait_truth_uuid.c b/src/testing/testing_api_trait_truth_uuid.c deleted file mode 100644 index 7eba4b0..0000000 --- a/src/testing/testing_api_trait_truth_uuid.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_api_trait_truth_uuid.c - * @brief traits to offer a UUID for some truth - * @author Christian Grothoff - * @author Dominik Meister - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - - -#define ANASTASIS_TESTING_TRAIT_TRUTH_UUID "anastasis-truth-uuid" - - -int -ANASTASIS_TESTING_get_trait_truth_uuid ( - const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthUUIDP **tpk) -{ - return cmd->traits (cmd->cls, - (const void **) tpk, - ANASTASIS_TESTING_TRAIT_TRUTH_UUID, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_truth_uuid ( - unsigned int index, - const struct ANASTASIS_CRYPTO_TruthUUIDP *tpk) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_TRUTH_UUID, - .ptr = (const void *) tpk - }; - - return ret; -} - - -/* end of testing_api_trait_truth_uuid.c */ diff --git a/src/testing/testing_api_traits.c b/src/testing/testing_api_traits.c new file mode 100644 index 0000000..79b78a5 --- /dev/null +++ b/src/testing/testing_api_traits.c @@ -0,0 +1,36 @@ +/* + This file is part of TALER + Copyright (C) 2018, 2021 Taler Systems SA + + 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, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_traits.c + * @brief loop for trait resolution + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "anastasis_testing_lib.h" +#include <taler/taler_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include <taler/taler_testing_lib.h> + + +ANASTASIS_TESTING_SIMPLE_TRAITS (ANASTASIS_TESTING_MAKE_IMPL_SIMPLE_TRAIT) + +ANASTASIS_TESTING_INDEXED_TRAITS (ANASTASIS_TESTING_MAKE_IMPL_INDEXED_TRAIT) + +/* end of testing_api_traits.c */ diff --git a/src/testing/testing_cmd_challenge_answer.c b/src/testing/testing_cmd_challenge_answer.c index ff897f3..ad24861 100644 --- a/src/testing/testing_cmd_challenge_answer.c +++ b/src/testing/testing_cmd_challenge_answer.c @@ -1,16 +1,16 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -20,7 +20,6 @@ * @author Dennis Neufeld * @author Dominik Meister */ - #include "platform.h" #include "anastasis_testing_lib.h" #include <taler/taler_util.h> @@ -28,6 +27,8 @@ #include <taler/taler_merchant_service.h> +// FIXME: break up into two files, one for start, one for answer! + /** * State for a "challenge answer" CMD. */ @@ -74,9 +75,14 @@ struct ChallengeState struct ANASTASIS_PaymentSecretP payment_order_req; /** - * Expected status code. + * Expected answer status code. + */ + enum ANASTASIS_ChallengeAnswerStatus expected_acs; + + /** + * Expected start status code. */ - enum ANASTASIS_ChallengeStatus expected_cs; + enum ANASTASIS_ChallengeStartStatus expected_scs; /** * Index of the challenge we are solving @@ -91,103 +97,35 @@ struct ChallengeState /** * code we read in the file generated by the plugin */ - char code[22]; + char *code; }; static void challenge_answer_cb (void *af_cls, - const struct ANASTASIS_ChallengeStartResponse *csr) + const struct ANASTASIS_ChallengeAnswerResponse *csr) { struct ChallengeState *cs = af_cls; cs->c = NULL; - if (csr->cs != cs->expected_cs) + if (csr->cs != cs->expected_acs) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Expected status %u, got %u\n", - cs->expected_cs, + cs->expected_acs, csr->cs); TALER_TESTING_interpreter_fail (cs->is); return; } switch (csr->cs) { - case ANASTASIS_CHALLENGE_STATUS_SOLVED: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED: break; - case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS: - { - FILE *file; - char *fn; - - if (0 == strcasecmp (csr->details.open_challenge.content_type, - "application/json")) - { - const char *filename; - json_t *in; - - in = json_loadb (csr->details.open_challenge.body, - csr->details.open_challenge.body_size, - JSON_REJECT_DUPLICATES, - NULL); - if (NULL == in) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (cs->is); - return; - } - filename = json_string_value (json_object_get (in, - "filename")); - if (NULL == filename) - { - GNUNET_break (0); - json_decref (in); - TALER_TESTING_interpreter_fail (cs->is); - return; - } - fn = GNUNET_strdup (filename); - json_decref (in); - } - else - { - fn = GNUNET_strndup (csr->details.open_challenge.body, - csr->details.open_challenge.body_size); - } - file = fopen (fn, - "r"); - if (NULL == file) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "open", - fn); - GNUNET_free (fn); - TALER_TESTING_interpreter_fail (cs->is); - return; - } - if (0 == fscanf (file, - "%21s", - cs->code)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "fscanf", - fn); - TALER_TESTING_interpreter_fail (cs->is); - fclose (file); - GNUNET_free (fn); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Read challenge answer `%s' from file `%s'\n", - cs->code, - fn); - TALER_TESTING_interpreter_next (cs->is); - GNUNET_break (0 == fclose (file)); - GNUNET_free (fn); - return; - } - case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER: + break; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED: if (0 != strncmp (csr->details.payment_required.taler_pay_uri, "taler+http://pay/", strlen ("taler+http://pay/"))) @@ -227,19 +165,13 @@ challenge_answer_cb (void *af_cls, } TALER_TESTING_interpreter_next (cs->is); return; - case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN: - break; - case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN: break; - case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE: GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; - case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED: - break; - case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT: - break; - case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED: break; } TALER_TESTING_interpreter_next (cs->is); @@ -259,7 +191,7 @@ challenge_answer_run (void *cls, struct TALER_TESTING_Interpreter *is) { struct ChallengeState *cs = cls; - const struct ANASTASIS_Challenge *c; + const struct ANASTASIS_Challenge **c; const struct ANASTASIS_PaymentSecretP *ps; cs->is = is; @@ -277,14 +209,15 @@ challenge_answer_run (void *cls, return; } if (GNUNET_OK != - ANASTASIS_TESTING_get_trait_challenge (ref, - cs->challenge_index, - &c)) + ANASTASIS_TESTING_get_trait_challenges (ref, + cs->challenge_index, + &c)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } + cs->c = (struct ANASTASIS_Challenge *) *c; } if (NULL != cs->payment_ref) @@ -301,7 +234,6 @@ challenge_answer_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_payment_secret (ref, - 0, &ps)) { GNUNET_break (0); @@ -314,8 +246,6 @@ challenge_answer_run (void *cls, ps = NULL; } - cs->c = (struct ANASTASIS_Challenge *) c; - if (1 == cs->mode) { const struct TALER_TESTING_Command *ref; @@ -333,7 +263,6 @@ challenge_answer_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_code (ref, - 0, &answer)) { GNUNET_break (0); @@ -384,6 +313,118 @@ challenge_answer_run (void *cls, } +static void +challenge_start_cb (void *af_cls, + const struct ANASTASIS_ChallengeStartResponse *csr) +{ + struct ChallengeState *cs = af_cls; + + cs->c = NULL; + if (csr->cs != cs->expected_scs) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected status %u, got %u\n", + cs->expected_scs, + csr->cs); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + switch (csr->cs) + { + case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED: + { + FILE *file; + char code[22]; + + file = fopen (csr->details.tan_filename, + "r"); + if (NULL == file) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "open", + csr->details.tan_filename); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + if (0 == fscanf (file, + "%21s", + code)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "fscanf", + csr->details.tan_filename); + GNUNET_break (0 == fclose (file)); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + GNUNET_break (0 == fclose (file)); + cs->code = GNUNET_strdup (code); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read code `%s'\n", + code); + } + break; + case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED: + GNUNET_break (0); /* FIXME: not implemented */ + break; + case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT: + GNUNET_break (0); /* FIXME: not implemented */ + break; + case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED: + GNUNET_break (0); /* FIXME: not implemented */ + break; + case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED: + if (0 != strncmp (csr->details.payment_required.taler_pay_uri, + "taler+http://pay/", + strlen ("taler+http://pay/"))) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid payment URI `%s'\n", + csr->details.payment_required.taler_pay_uri); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + cs->payment_uri = GNUNET_strdup ( + csr->details.payment_required.taler_pay_uri); + { + struct TALER_MERCHANT_PayUriData pud; + + if (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (cs->payment_uri, + &pud)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + cs->order_id = GNUNET_strdup (pud.order_id); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (cs->order_id, + strlen (cs->order_id), + &cs->payment_order_req, + sizeof (cs->payment_order_req))) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + TALER_MERCHANT_parse_pay_uri_free (&pud); + } + TALER_TESTING_interpreter_next (cs->is); + return; + case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN: + break; + case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE: + GNUNET_break (0); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + TALER_TESTING_interpreter_next (cs->is); +} + + /** * Run a "recover secret" CMD. * @@ -397,7 +438,7 @@ challenge_start_run (void *cls, struct TALER_TESTING_Interpreter *is) { struct ChallengeState *cs = cls; - const struct ANASTASIS_Challenge *c; + const struct ANASTASIS_Challenge **c; const struct TALER_TESTING_Command *ref; const struct ANASTASIS_PaymentSecretP *ps; @@ -412,9 +453,9 @@ challenge_start_run (void *cls, return; } if (GNUNET_OK != - ANASTASIS_TESTING_get_trait_challenge (ref, - cs->challenge_index, - &c)) + ANASTASIS_TESTING_get_trait_challenges (ref, + cs->challenge_index, + &c)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); @@ -434,7 +475,6 @@ challenge_start_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_payment_secret (ref, - 0, &ps)) { GNUNET_break (0); @@ -447,11 +487,9 @@ challenge_start_run (void *cls, ps = NULL; } if (GNUNET_OK != - ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) c, + ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) *c, ps, - GNUNET_TIME_UNIT_ZERO, - NULL, - &challenge_answer_cb, + &challenge_start_cb, cs)) { GNUNET_break (0); @@ -484,6 +522,7 @@ challenge_cleanup (void *cls, } GNUNET_free (cs->payment_uri); GNUNET_free (cs->order_id); + GNUNET_free (cs->code); GNUNET_free (cs); } @@ -497,7 +536,7 @@ challenge_cleanup (void *cls, * @param index index number of the object to extract. * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue challenge_create_traits (void *cls, const void **ret, const char *trait, @@ -505,14 +544,11 @@ challenge_create_traits (void *cls, { struct ChallengeState *cs = cls; struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_code (0, - cs->code), - ANASTASIS_TESTING_make_trait_payment_secret (0, - &cs->payment_order_req), - TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL, - cs->payment_uri), - TALER_TESTING_make_trait_order_id (0, - cs->order_id), + ANASTASIS_TESTING_make_trait_code (cs->code), + ANASTASIS_TESTING_make_trait_payment_secret ( + &cs->payment_order_req), + TALER_TESTING_make_trait_payto_uri (cs->payment_uri), + TALER_TESTING_make_trait_order_id (cs->order_id), TALER_TESTING_trait_end () }; @@ -529,12 +565,12 @@ ANASTASIS_TESTING_cmd_challenge_start ( const char *payment_ref, const char *challenge_ref, unsigned int challenge_index, - enum ANASTASIS_ChallengeStatus expected_cs) + enum ANASTASIS_ChallengeStartStatus expected_cs) { struct ChallengeState *cs; cs = GNUNET_new (struct ChallengeState); - cs->expected_cs = expected_cs; + cs->expected_scs = expected_cs; cs->challenge_ref = challenge_ref; cs->payment_ref = payment_ref; cs->challenge_index = challenge_index; @@ -560,12 +596,12 @@ ANASTASIS_TESTING_cmd_challenge_answer ( unsigned int challenge_index, const char *answer, unsigned int mode, - enum ANASTASIS_ChallengeStatus expected_cs) + enum ANASTASIS_ChallengeAnswerStatus expected_cs) { struct ChallengeState *cs; cs = GNUNET_new (struct ChallengeState); - cs->expected_cs = expected_cs; + cs->expected_acs = expected_cs; cs->challenge_ref = challenge_ref; cs->payment_ref = payment_ref; cs->answer = answer; diff --git a/src/testing/testing_cmd_policy_create.c b/src/testing/testing_cmd_policy_create.c index 62ad71f..64f7060 100644 --- a/src/testing/testing_cmd_policy_create.c +++ b/src/testing/testing_cmd_policy_create.c @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -82,7 +82,7 @@ policy_create_run (void *cls, for (unsigned int i = 0; i < pcs->cmd_label_array_length; i++) { const struct TALER_TESTING_Command *ref; - const struct ANASTASIS_Truth *truth; + const struct ANASTASIS_Truth **truth; ref = TALER_TESTING_interpreter_lookup_command (is, pcs->cmd_label_array[i]); @@ -94,15 +94,14 @@ policy_create_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_truth (ref, - 0, &truth)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (pcs->is); return; } - GNUNET_assert (NULL != truth); - truths[i] = truth; + GNUNET_assert (NULL != *truth); + truths[i] = *truth; } } @@ -159,8 +158,8 @@ policy_create_traits (void *cls, { struct PolicyCreateState *pcs = cls; struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_policy (0, - pcs->policy), + ANASTASIS_TESTING_make_trait_policy ( + (const struct ANASTASIS_Policy **) &pcs->policy), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_cmd_recover_secret.c b/src/testing/testing_cmd_recover_secret.c index 55c0168..1f3e832 100644 --- a/src/testing/testing_cmd_recover_secret.c +++ b/src/testing/testing_cmd_recover_secret.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -67,11 +67,6 @@ struct RecoverSecretState json_t *id_data; /** - * Salt to be used to derive the id - */ - struct ANASTASIS_CRYPTO_ProviderSaltP *salt; - - /** * Recovery information from the lookup */ struct ANASTASIS_RecoveryInformation *ri; @@ -113,13 +108,19 @@ policy_lookup_cb (void *cls, { struct RecoverSecretState *rss = cls; - rss->ri = (struct ANASTASIS_RecoveryInformation *) ri; if (NULL == ri) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } + if (0 == ri->cs_len) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rss->is); + return; + } + rss->ri = (struct ANASTASIS_RecoveryInformation *) ri; TALER_TESTING_interpreter_next (rss->is); } @@ -188,14 +189,14 @@ recover_secret_run (void *cls, { struct RecoverSecretState *rss = cls; const struct TALER_TESTING_Command *ref; - const struct ANASTASIS_CRYPTO_ProviderSaltP *salt; + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt; rss->is = is; if (NULL != rss->download_reference) { - ref = TALER_TESTING_interpreter_lookup_command - (is, - rss->download_reference); + ref = TALER_TESTING_interpreter_lookup_command ( + is, + rss->download_reference); if (NULL == ref) { GNUNET_break (0); @@ -203,9 +204,8 @@ recover_secret_run (void *cls, return; } if (GNUNET_OK != - ANASTASIS_TESTING_get_trait_salt (ref, - 0, - &salt)) + ANASTASIS_TESTING_get_trait_provider_salt (ref, + &provider_salt)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); @@ -224,24 +224,25 @@ recover_secret_run (void *cls, return; } if (GNUNET_OK != - ANASTASIS_TESTING_get_trait_core_secret (ref, - 0, - &rss->core_secret)) + ANASTASIS_TESTING_get_trait_core_secret ( + ref, + &rss->core_secret)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } } - rss->recovery = ANASTASIS_recovery_begin (is->ctx, - rss->id_data, - rss->version, - rss->anastasis_url, - salt, - &policy_lookup_cb, - rss, - &core_secret_cb, - rss); + rss->recovery = ANASTASIS_recovery_begin ( + TALER_TESTING_interpreter_get_context (is), + rss->id_data, + rss->version, + rss->anastasis_url, + provider_salt, + &policy_lookup_cb, + rss, + &core_secret_cb, + rss); if (NULL == rss->recovery) { GNUNET_break (0); @@ -307,7 +308,7 @@ recover_secret_cleanup (void *cls, * @param index index number of the object to extract. * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue recover_secret_traits (void *cls, const void **ret, const char *trait, @@ -327,8 +328,9 @@ recover_secret_traits (void *cls, } { struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_challenge (index, - rss->ri->cs[index]), + ANASTASIS_TESTING_make_trait_challenges ( + index, + (const struct ANASTASIS_Challenge **) &rss->ri->cs[index]), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_cmd_secret_share.c b/src/testing/testing_cmd_secret_share.c index d9122e8..3c401d2 100644 --- a/src/testing/testing_cmd_secret_share.c +++ b/src/testing/testing_cmd_secret_share.c @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -135,14 +135,7 @@ secret_share_result_cb (void *cls, sss->sso = NULL; if (sr->ss != sss->want_status) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - sr->ss, - sss->is->commands[sss->is->ip].label, - __FILE__, - __LINE__); TALER_TESTING_interpreter_fail (sss->is); - return; } switch (sr->ss) { @@ -199,7 +192,7 @@ secret_share_run (void *cls, for (unsigned int i = 0; i < sss->cmd_label_array_length; i++) { const struct TALER_TESTING_Command *ref; - const struct ANASTASIS_Policy *policy; + const struct ANASTASIS_Policy **policy; ref = TALER_TESTING_interpreter_lookup_command (is, sss->cmd_label_array[i]); @@ -211,15 +204,14 @@ secret_share_run (void *cls, } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_policy (ref, - 0, &policy)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (sss->is); return; } - GNUNET_assert (NULL != policy); - policies[i] = policy; + GNUNET_assert (NULL != *policy); + policies[i] = *policy; } } @@ -238,15 +230,13 @@ secret_share_run (void *cls, } if (GNUNET_OK != TALER_TESTING_get_trait_order_id (ref, - 0, &order_id)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (sss->is); return; } - sss->payment_order_id = (char *) order_id; - + sss->payment_order_id = GNUNET_strdup (order_id); if (NULL == sss->payment_order_id) { GNUNET_break (0); @@ -277,7 +267,7 @@ secret_share_run (void *cls, pds.provider_url = sss->anastasis_url; { const struct TALER_TESTING_Command *ref; - const struct ANASTASIS_CRYPTO_ProviderSaltP *salt; + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt; ref = TALER_TESTING_interpreter_lookup_command (is, sss->config_ref); @@ -288,30 +278,30 @@ secret_share_run (void *cls, return; } if (GNUNET_OK != - ANASTASIS_TESTING_get_trait_salt (ref, - 0, - &salt)) + ANASTASIS_TESTING_get_trait_provider_salt (ref, + &provider_salt)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (sss->is); return; } - pds.provider_salt = *salt; + pds.provider_salt = *provider_salt; } - sss->sso = ANASTASIS_secret_share (is->ctx, - sss->id_data, - &pds, - 1, - policies, - sss->cmd_label_array_length, - false, - GNUNET_TIME_UNIT_ZERO, - &secret_share_result_cb, - sss, - "test-case", - sss->core_secret, - sss->core_secret_size); + sss->sso = ANASTASIS_secret_share ( + TALER_TESTING_interpreter_get_context (is), + sss->id_data, + &pds, + 1, + policies, + sss->cmd_label_array_length, + false, + GNUNET_TIME_UNIT_ZERO, + &secret_share_result_cb, + sss, + "test-case", + sss->core_secret, + sss->core_secret_size); if (NULL == sss->sso) { GNUNET_break (0); @@ -368,12 +358,9 @@ secret_share_traits (void *cls, { struct SecretShareState *sss = cls; struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_claim_token (0, - &sss->token), - ANASTASIS_TESTING_make_trait_core_secret (0, - sss->core_secret), - TALER_TESTING_make_trait_order_id (0, - sss->payment_order_id), + TALER_TESTING_make_trait_claim_token (&sss->token), + ANASTASIS_TESTING_make_trait_core_secret (sss->core_secret), + TALER_TESTING_make_trait_order_id (sss->payment_order_id), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_cmd_truth_upload.c b/src/testing/testing_cmd_truth_upload.c index 19692c8..2e3523b 100644 --- a/src/testing/testing_cmd_truth_upload.c +++ b/src/testing/testing_cmd_truth_upload.c @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -181,15 +181,15 @@ truth_upload_run (void *cls, { struct TruthUploadState *tus = cls; const struct TALER_TESTING_Command *ref; - const struct ANASTASIS_CRYPTO_ProviderSaltP *salt; + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt; struct ANASTASIS_CRYPTO_UserIdentifierP user_id; tus->is = is; if (NULL != tus->salt_reference) { - ref = TALER_TESTING_interpreter_lookup_command - (is, - tus->salt_reference); + ref = TALER_TESTING_interpreter_lookup_command ( + is, + tus->salt_reference); if (NULL == ref) { GNUNET_break (0); @@ -197,33 +197,31 @@ truth_upload_run (void *cls, return; } if (GNUNET_OK != - ANASTASIS_TESTING_get_trait_salt (ref, - 0, - &salt)) + ANASTASIS_TESTING_get_trait_provider_salt (ref, + &provider_salt)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (tus->is); return; } } - ANASTASIS_CRYPTO_user_identifier_derive (tus->id_data, - salt, + provider_salt, &user_id); - - tus->tuo = ANASTASIS_truth_upload (is->ctx, - &user_id, - tus->anastasis_url, - tus->method, - tus->instructions, - tus->mime_type, - salt, - tus->truth_data, - tus->truth_data_size, - false, /* force payment */ - GNUNET_TIME_UNIT_ZERO, - &truth_upload_cb, - tus); + tus->tuo = ANASTASIS_truth_upload ( + TALER_TESTING_interpreter_get_context (is), + &user_id, + tus->anastasis_url, + tus->method, + tus->instructions, + tus->mime_type, + provider_salt, + tus->truth_data, + tus->truth_data_size, + false, /* force payment */ + GNUNET_TIME_UNIT_ZERO, + &truth_upload_cb, + tus); if (NULL == tus->tuo) { GNUNET_break (0); @@ -285,10 +283,9 @@ truth_upload_traits (void *cls, { struct TruthUploadState *tus = cls; struct TALER_TESTING_Trait traits[] = { - ANASTASIS_TESTING_make_trait_truth (0, - tus->truth), - ANASTASIS_TESTING_make_trait_payment_secret (0, - &tus->payment_secret_response), + ANASTASIS_TESTING_make_trait_truth ( + (const struct ANASTASIS_Truth **) &tus->truth), + ANASTASIS_TESTING_make_trait_payment_secret (&tus->payment_secret_response), TALER_TESTING_trait_end () }; diff --git a/src/testing/testing_trait_challenge.c b/src/testing/testing_trait_challenge.c deleted file mode 100644 index f0485a1..0000000 --- a/src/testing/testing_trait_challenge.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_trait_challenge.c - * @brief traits to offer a challenge - * @author Christian Grothoff - * @author Dominik Meister - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_CHALLENGE "anastasis-challenge" - - -int -ANASTASIS_TESTING_get_trait_challenge (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_Challenge **c) -{ - return cmd->traits (cmd->cls, - (const void **) c, - ANASTASIS_TESTING_TRAIT_CHALLENGE, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_challenge (unsigned int index, - const struct ANASTASIS_Challenge *c) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_CHALLENGE, - .ptr = (const void *) c - }; - return ret; -} - - -/* end of testing_trait_challenge.c */ diff --git a/src/testing/testing_trait_core_secret.c b/src/testing/testing_trait_core_secret.c deleted file mode 100644 index e6aee0f..0000000 --- a/src/testing/testing_trait_core_secret.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_trait_core_secret.c - * @brief traits to offer the core secret - * @author Christian Grothoff - * @author Dominik Meister - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_CORE_SECRET "anastasis-core-secret" - - -int -ANASTASIS_TESTING_get_trait_core_secret (const struct - TALER_TESTING_Command *cmd, - unsigned int index, - const void **s) -{ - return cmd->traits (cmd->cls, - s, - ANASTASIS_TESTING_TRAIT_CORE_SECRET, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_core_secret (unsigned int index, - const void *s) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_CORE_SECRET, - .ptr = s - }; - - return ret; -} - - -/* end of testing_trait_core_secret.c */ diff --git a/src/testing/testing_trait_policy.c b/src/testing/testing_trait_policy.c deleted file mode 100644 index 0944088..0000000 --- a/src/testing/testing_trait_policy.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_trait_policy.c - * @brief traits to offer a policy - * @author Christian Grothoff - * @author Dominik Meister - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_POLICY "anastasis-policy" - - -int -ANASTASIS_TESTING_get_trait_policy (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_Policy **p) -{ - return cmd->traits (cmd->cls, - (const void **) p, - ANASTASIS_TESTING_TRAIT_POLICY, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_policy (unsigned int index, - const struct ANASTASIS_Policy *p) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_POLICY, - .ptr = (const void *) p - }; - - return ret; -} - - -/* end of testing_trait_policy.c */ diff --git a/src/testing/testing_trait_truth.c b/src/testing/testing_trait_truth.c deleted file mode 100644 index 559fd9e..0000000 --- a/src/testing/testing_trait_truth.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL - - Anastasis 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. - - Anastasis 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 Affero General Public - License along with Anastasis; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file testing/testing_trait_truth.c - * @brief traits to offer a truth - * @author Christian Grothoff - * @author Dominik Meister - * @author Dennis Neufeld - */ -#include "platform.h" -#include "anastasis_testing_lib.h" - -#define ANASTASIS_TESTING_TRAIT_TRUTH "anastasis-truth" - - -int -ANASTASIS_TESTING_get_trait_truth (const struct TALER_TESTING_Command *cmd, - unsigned int index, - const struct ANASTASIS_Truth **t) -{ - return cmd->traits (cmd->cls, - (const void **) t, - ANASTASIS_TESTING_TRAIT_TRUTH, - index); -} - - -struct TALER_TESTING_Trait -ANASTASIS_TESTING_make_trait_truth (unsigned int index, - const struct ANASTASIS_Truth *t) -{ - struct TALER_TESTING_Trait ret = { - .index = index, - .trait_name = ANASTASIS_TESTING_TRAIT_TRUTH, - .ptr = (const void *) t - }; - - return ret; -} - - -/* end of testing_trait_truth.c */ diff --git a/src/util/.gitignore b/src/util/.gitignore new file mode 100644 index 0000000..80af6f7 --- /dev/null +++ b/src/util/.gitignore @@ -0,0 +1 @@ +anastasis-crypto-tvg diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 657ec0c..29d2b13 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -6,6 +6,9 @@ if USE_COVERAGE XLIB = -lgcov endif +bin_PROGRAMS = \ + anastasis-crypto-tvg + pkgcfgdir = $(prefix)/share/anastasis/config.d/ pkgcfg_DATA = \ @@ -23,6 +26,9 @@ anastasis-config: anastasis-config.in chmod a-w+x $@.tmp && \ mv $@.tmp $@ +CLEANFILES = \ + anastasis-config + bin_SCRIPTS = \ anastasis-config @@ -31,10 +37,12 @@ lib_LTLIBRARIES = \ libanastasisutil_la_SOURCES = \ anastasis_crypto.c \ - os_installation.c + os_installation.c \ + pin.c libanastasisutil_la_LIBADD = \ -lgnunetutil \ $(LIBGCRYPT_LIBS) \ + -lsodium \ -ljansson \ -ltalerutil \ $(XLIB) @@ -51,7 +59,18 @@ TESTS = \ test_anastasis_crypto_SOURCES = \ test_anastasis_crypto.c test_anastasis_crypto_LDADD = \ + $(top_builddir)/src/util/libanastasisutil.la \ -lgnunetutil \ -ltalerutil \ + $(XLIB) + +anastasis_crypto_tvg_SOURCES = \ + anastasis-crypto-tvg.c +anastasis_crypto_tvg_LDADD = \ libanastasisutil.la \ + -ltalerjson \ + -ltalerutil \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ $(XLIB) diff --git a/src/util/anastasis-config.c b/src/util/anastasis-config.c index 0c2cb29..34574d1 100644 --- a/src/util/anastasis-config.c +++ b/src/util/anastasis-config.c @@ -3,16 +3,16 @@ Copyright (C) 2012-2021 Anastasis Systems SA Anastasis is free software: you can redistribute it and/or modify it - under the terms of the GNU Affero General Public License as published + under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Anastasis 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. + General Public License for more details. - You should have received a copy of the GNU Affero General Public License + You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. SPDX-License-Identifier: AGPL3.0-or-later diff --git a/src/util/anastasis-config.in b/src/util/anastasis-config.in index 0e94921..6657540 100644 --- a/src/util/anastasis-config.in +++ b/src/util/anastasis-config.in @@ -7,6 +7,7 @@ if ! type gnunet-config >/dev/null; then exit 1 fi -GC=`which gnunet-config` -export LD_PRELOAD=${LD_PRELOAD:-}:%libdir%/libanastasisutil.so +GC=$(which gnunet-config) +SO=$(ls %libdir%/libanastasisutil.so.* | sort -n | tail -n1) +export LD_PRELOAD=${LD_PRELOAD:-}:${SO} exec gnunet-config "$@" diff --git a/src/util/anastasis-crypto-tvg.c b/src/util/anastasis-crypto-tvg.c new file mode 100644 index 0000000..d5fc4c1 --- /dev/null +++ b/src/util/anastasis-crypto-tvg.c @@ -0,0 +1,601 @@ +/* + This file is part of Anastasis + Copyright (C) 2020,2021 Anastasis SARL + + Anastasis 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. + + Anastasis 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 + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ + + +/** + * @file util/anastasis-crypto-tgv.c + * @brief Generate test vectors for cryptographic operations. + * @author Florian Dold + * + * + * Test vectors have the following format (TypeScript pseudo code): + * + * interface TestVectorFile { + * encoding: "base32crockford"; + * producer?: string; + * vectors: TestVector[]; + * } + * + * enum Operation { + * Hash("hash"), + * ... + * } + * + * interface TestVector { + * operation: Operation; + * // Inputs for the operation + * [ k: string]: string | number; + * }; + * + * + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_signatures.h> +#include <gnunet/gnunet_testing_lib.h> +#include <jansson.h> +#include <gcrypt.h> +#include "anastasis_crypto_lib.h" + + +/** + * Should we verify or output test vectors? + */ +static int verify_flag = GNUNET_NO; + + +/** + * Global exit code. + */ +static int global_ret = 0; + + +/** + * Create a fresh test vector for a given operation label. + * + * @param vecs array of vectors to append the new vector to + * @param vecname label for the operation of the vector + * @returns the fresh test vector + */ +static json_t * +vec_for (json_t *vecs, const char *vecname) +{ + json_t *t = json_object (); + + json_object_set_new (t, + "operation", + json_string (vecname)); + json_array_append_new (vecs, t); + return t; +} + + +/** + * Add a base32crockford encoded value + * to a test vector. + * + * @param vec test vector to add to + * @param label label for the value + * @param data data to add + * @param size size of data + */ +static void +d2j (json_t *vec, + const char *label, + const void *data, + size_t size) +{ + char *buf; + json_t *json; + + buf = GNUNET_STRINGS_data_to_string_alloc (data, size); + json = json_string (buf); + GNUNET_free (buf); + GNUNET_break (NULL != json); + + json_object_set_new (vec, label, json); +} + + +static void +d2j_append (json_t *arr, + const void *data, + size_t size) +{ + char *buf; + json_t *json; + + buf = GNUNET_STRINGS_data_to_string_alloc (data, size); + json = json_string (buf); + GNUNET_free (buf); + GNUNET_break (NULL != json); + + json_array_append_new (arr, + json); +} + + +#define d2j_auto(vec, label, d) d2j (vec, label, d, sizeof (*d)) +#define d2j_append_auto(arr, d) d2j_append (arr, d, sizeof (*d)) +#define random_auto(d) GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, \ + d, \ + sizeof (*d)); + + +static int +expect_data_fixed (json_t *vec, + const char *name, + void *data, + size_t expect_len) +{ + const char *s = json_string_value (json_object_get (vec, name)); + + if (NULL == s) + return GNUNET_NO; + + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (s, + strlen (s), + data, + expect_len)) + return GNUNET_NO; + return GNUNET_OK; +} + + +static int +expect_data_dynamic (json_t *vec, + const char *name, + void **data, + size_t *ret_len) +{ + const char *s = json_string_value (json_object_get (vec, name)); + char *tmp; + size_t len; + + if (NULL == s) + return GNUNET_NO; + + len = (strlen (s) * 5) / 8; + if (NULL != ret_len) + *ret_len = len; + tmp = GNUNET_malloc (len); + + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (s, strlen (s), tmp, len)) + { + GNUNET_free (tmp); + return GNUNET_NO; + } + *data = tmp; + return GNUNET_OK; +} + + +/** + * Check a single vector. + * + * @param operation operator of the vector + * @param vec the vector, a JSON object. + * + * @returns GNUNET_OK if the vector is okay + */ +static int +checkvec (const char *operation, + json_t *vec) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "checking %s\n", operation); + + if (0 == strcmp (operation, "hash")) + { + void *data; + size_t data_len; + struct GNUNET_HashCode hash_out; + struct GNUNET_HashCode hc; + + if (GNUNET_OK != expect_data_dynamic (vec, + "input", + &data, + &data_len)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != expect_data_fixed (vec, + "output", + &hash_out, + sizeof (hash_out))) + { + GNUNET_free (data); + GNUNET_break (0); + return GNUNET_NO; + } + + GNUNET_CRYPTO_hash (data, data_len, &hc); + + if (0 != GNUNET_memcmp (&hc, &hash_out)) + { + GNUNET_free (data); + GNUNET_break (0); + return GNUNET_NO; + } + GNUNET_free (data); + } + + return GNUNET_OK; +} + + +/** + * Check test vectors from stdin. + * + * @returns global exit code + */ +static int +check_vectors () +{ + json_error_t err; + json_t *vecfile = json_loadf (stdin, 0, &err); + const char *encoding; + json_t *vectors; + + if (NULL == vecfile) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unable to parse JSON\n"); + return 1; + } + encoding = json_string_value (json_object_get (vecfile, + "encoding")); + if ( (NULL == encoding) || (0 != strcmp (encoding, "base32crockford")) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unsupported or missing encoding\n"); + json_decref (vecfile); + return 1; + } + vectors = json_object_get (vecfile, "vectors"); + if (! json_is_array (vectors)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "bad vectors\n"); + json_decref (vecfile); + return 1; + } + { + /* array is a JSON array */ + size_t index; + json_t *value; + int ret; + + json_array_foreach (vectors, index, value) { + const char *op = json_string_value (json_object_get (value, + "operation")); + + if (NULL == op) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "missing operation\n"); + ret = GNUNET_SYSERR; + break; + } + ret = checkvec (op, value); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "bad vector %u\n", + (unsigned int) index); + break; + } + } + return (ret == GNUNET_OK) ? 0 : 1; + } +} + + +/** + * Output test vectors. + * + * @returns global exit code + */ +static int +output_vectors () +{ + json_t *vecfile = json_object (); + json_t *vecs = json_array (); + + json_object_set_new (vecfile, + "encoding", + json_string ("base32crockford")); + json_object_set_new (vecfile, + "producer", + json_string ( + "GNU Anastasis (C implementation) " PACKAGE_VERSION " " + VCS_VERSION)); + json_object_set_new (vecfile, + "vectors", + vecs); + + { + json_t *vec = vec_for (vecs, "hash"); + struct GNUNET_HashCode hc; + char *str = "Hello, GNUnet"; + + GNUNET_CRYPTO_hash (str, strlen (str), &hc); + + d2j (vec, "input", str, strlen (str)); + d2j (vec, "output", &hc, sizeof (struct GNUNET_HashCode)); + } + + { + json_t *vec = vec_for (vecs, "user_identifier_derive"); + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + struct ANASTASIS_CRYPTO_UserIdentifierP id; + json_t *id_data = json_pack ("{s:s, s:s}", + "name", + "Fleabag", + "ssn", + "AB123"); + GNUNET_assert (NULL != id_data); + random_auto (&provider_salt); + + ANASTASIS_CRYPTO_user_identifier_derive (id_data, + &provider_salt, + &id); + json_object_set_new (vec, "input_id_data", id_data); + d2j_auto (vec, "input_provider_salt", &provider_salt); + d2j_auto (vec, "output_id", &id); + } + + { + json_t *vec = vec_for (vecs, "account_keypair_derive"); + struct ANASTASIS_CRYPTO_UserIdentifierP id; + struct ANASTASIS_CRYPTO_AccountPrivateKeyP priv_key; + struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key; + + random_auto (&id); + ANASTASIS_CRYPTO_account_public_key_derive (&id, &pub_key); + ANASTASIS_CRYPTO_account_private_key_derive (&id, &priv_key); + + d2j_auto (vec, "input_id", &id); + d2j_auto (vec, "output_priv_key", &priv_key); + d2j_auto (vec, "output_pub_key", &pub_key); + + } + + { + json_t *vec = vec_for (vecs, "secure_answer_hash"); + const char *answer = "Blah"; + struct ANASTASIS_CRYPTO_TruthUUIDP uuid; + struct ANASTASIS_CRYPTO_QuestionSaltP salt; + struct GNUNET_HashCode result; + + random_auto (&uuid); + random_auto (&salt); + ANASTASIS_CRYPTO_secure_answer_hash (answer, &uuid, &salt, &result); + json_object_set_new (vec, "input_answer", json_string (answer)); + d2j_auto (vec, "input_uuid", &uuid); + d2j_auto (vec, "input_salt", &salt); + d2j_auto (vec, "output_hash", &result); + } + + { + json_t *vec = vec_for (vecs, "recovery_document_encryption"); + struct ANASTASIS_CRYPTO_UserIdentifierP id; + void *rec_doc = "my recovery doc"; + size_t rd_size = strlen (rec_doc) + 1; + void *enc_rec_doc; + size_t erd_size; + + random_auto (&id); + + ANASTASIS_CRYPTO_recovery_document_encrypt (&id, + rec_doc, + rd_size, + &enc_rec_doc, + &erd_size); + d2j_auto (vec, "input_user_id", &id); + d2j (vec, "input_recovery_document", rec_doc, rd_size); + d2j (vec, "output_encrypted_recovery_document", &enc_rec_doc, erd_size); + } + + { + /* With extra salt */ + json_t *vec = vec_for (vecs, "keyshare_encryption"); + struct ANASTASIS_CRYPTO_KeyShareP key_share; + struct ANASTASIS_CRYPTO_UserIdentifierP id; + char *xsalt = "myanswer"; + struct ANASTASIS_CRYPTO_EncryptedKeyShareP enc_key_share; + + random_auto (&key_share); + random_auto (&id); + + ANASTASIS_CRYPTO_keyshare_encrypt (&key_share, + &id, + xsalt, + &enc_key_share); + d2j_auto (vec, "input_key_share", &key_share); + d2j_auto (vec, "input_user_id", &id); + json_object_set_new (vec, "input_xsalt", json_string (xsalt)); + d2j_auto (vec, "output_enc_key_share", &enc_key_share); + } + + { + /* Without extra salt */ + json_t *vec = vec_for (vecs, "keyshare_encryption"); + struct ANASTASIS_CRYPTO_KeyShareP key_share; + struct ANASTASIS_CRYPTO_UserIdentifierP id; + char *xsalt = NULL; + struct ANASTASIS_CRYPTO_EncryptedKeyShareP enc_key_share; + + random_auto (&key_share); + random_auto (&id); + + ANASTASIS_CRYPTO_keyshare_encrypt (&key_share, + &id, + xsalt, + &enc_key_share); + d2j_auto (vec, "input_key_share", &key_share); + d2j_auto (vec, "input_user_id", &id); + json_object_set_new (vec, "input_xsalt", json_null ()); + d2j_auto (vec, "output_enc_key_share", &enc_key_share); + } + + { + json_t *vec = vec_for (vecs, "truth_encryption"); + + struct ANASTASIS_CRYPTO_NonceP nonce; + struct ANASTASIS_CRYPTO_TruthKeyP truth_enc_key; + char truth[256]; + size_t truth_size = 256; + void *enc_truth; + size_t ect_size; + + random_auto (&nonce); + random_auto (&truth); + random_auto (&truth_enc_key); + + ANASTASIS_CRYPTO_truth_encrypt (&nonce, + &truth_enc_key, + truth, + truth_size, + &enc_truth, + &ect_size); + + d2j_auto (vec, "input_nonce", &nonce); + d2j_auto (vec, "input_truth_enc_key", &truth_enc_key); + d2j (vec, "input_truth", &truth, truth_size); + d2j (vec, "output_encrypted_truth", enc_truth, ect_size); + } + + { + json_t *vec = vec_for (vecs, "policy_key_derive"); + + struct ANASTASIS_CRYPTO_KeyShareP key_shares[2]; + unsigned int keyshare_length = 2; + struct ANASTASIS_CRYPTO_MasterSaltP salt; + struct ANASTASIS_CRYPTO_PolicyKeyP policy_key; + json_t *key_shares_json = json_array (); + + random_auto (&key_shares[0]); + random_auto (&key_shares[1]); + random_auto (&salt); + + ANASTASIS_CRYPTO_policy_key_derive (key_shares, + keyshare_length, + &salt, + &policy_key); + + d2j_append_auto (key_shares_json, &key_shares[0]); + d2j_append_auto (key_shares_json, &key_shares[1]); + json_object_set_new (vec, "input_key_shares", key_shares_json); + d2j_auto (vec, "input_salt", &salt); + d2j_auto (vec, "output_policy_key", &policy_key); + } + + { + // json_t *vec = vec_for (vecs, "core_secret_encryption"); + // struct ANASTASIS_CRYPTO_PolicyKeyP policy_keys[2]; + // unsigned int policy_keys_length = 2; + // char core_secret[256]; + // size_t core_secret_size = 256; + // void *enc_core_secret; + // struct ANASTASIS_CRYPTO_EncryptedMasterKeyP encrypted_master_keys[2]; + // json_t *policy_keys_json = json_array (); + // json_t *encrypted_master_keys_json = json_array (); + + // random_auto (&policy_keys[0]); + // random_auto (&policy_keys[1]); + // random_auto (&core_secret); + + // ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys, policy_keys_length, + // core_secret, core_secret_size, + // &enc_core_secret, + // encrypted_master_keys); + + // d2j_append_auto (policy_keys_json, &policy_keys_json[0]); + // d2j_append_auto (policy_keys_json, &policy_keys_json[1]); + // d2j_append_auto (encrypted_master_keys_json, &encrypted_master_keys[0]); + // d2j_append_auto (encrypted_master_keys_json, &encrypted_master_keys[1]); + + // d2j_auto (vec, "input_core_secret", &core_secret); + // json_object_set_new (vec, "input_policy_keys", policy_keys_json); + // json_object_set_new (vec, "output_encrypted_core_secret", encrypted_master_keys_json); + // json_object_set_new (vec, "output_encrypted_master_keys", encrypted_master_keys_json); + } + + + json_dumpf (vecfile, stdout, JSON_INDENT (2)); + json_decref (vecfile); + printf ("\n"); + + return 0; +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + if (GNUNET_YES == verify_flag) + global_ret = check_vectors (); + else + global_ret = output_vectors (); +} + + +/** + * The main function of the test vector generation tool. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_flag ('V', + "verify", + gettext_noop ( + "verify a test vector from stdin"), + &verify_flag), + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("anastasis-crypto-tvg", + "INFO", + NULL)); + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "anastasis-crypto-tvg", + "Generate test vectors for cryptographic operations", + options, + &run, NULL)) + return 1; + return global_ret; +} + + +/* end of anastasis-crypto-tvg.c */ diff --git a/src/util/anastasis_crypto.c b/src/util/anastasis_crypto.c index bed0a94..579f097 100644 --- a/src/util/anastasis_crypto.c +++ b/src/util/anastasis_crypto.c @@ -3,14 +3,14 @@ Copyright (C) 2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis 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. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -41,6 +41,10 @@ ANASTASIS_hash_answer (uint64_t code, GNUNET_CRYPTO_hash (cbuf, strlen (cbuf), hashed_code); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Hashed answer %llu to %s\n", + (unsigned long long) code, + GNUNET_h2s (hashed_code)); } @@ -61,61 +65,53 @@ ANASTASIS_CRYPTO_secure_answer_hash ( GNUNET_CRYPTO_kdf ( result, sizeof (*result), - "Anastasis-secure-question-uuid-salting", - strlen ("Anastasis-secure-question-uuid-salting"), - &pow, - sizeof (pow), + /* salt / XTS */ uuid, sizeof (*uuid), + /* skm */ + &pow, + sizeof (pow), + /* info chunks */ + "anastasis-secure-question-hashing", + strlen ("anastasis-secure-question-hashing"), NULL, 0)); } /** - * Compute @a key and @a iv. + * Compute @a key. * * @param key_material key for calculation * @param key_m_len length of key * @param nonce nonce for calculation * @param salt salt value for calculation * @param[out] key where to write the en-/description key - * @param[out] iv where to write the IV */ static void -get_iv_key (const void *key_material, +derive_key (const void *key_material, size_t key_m_len, const struct ANASTASIS_CRYPTO_NonceP *nonce, const char *salt, - const struct ANASTASIS_CRYPTO_SymKeyP *key, - struct ANASTASIS_CRYPTO_IvP *iv) + struct ANASTASIS_CRYPTO_SymKeyP *key) { - char res[sizeof (struct ANASTASIS_CRYPTO_SymKeyP) - + sizeof (struct ANASTASIS_CRYPTO_IvP)]; - - if (GNUNET_YES != - GNUNET_CRYPTO_hkdf (res, - sizeof (res), - GCRY_MD_SHA512, - GCRY_MD_SHA256, - key_material, - key_m_len, - nonce, - sizeof (struct ANASTASIS_CRYPTO_NonceP), - salt, - strlen (salt), - NULL, - 0)) - { - GNUNET_break (0); - return; - } - memcpy ((void *) key, - res, - sizeof (*key)); - memcpy (iv, - &res[sizeof (*key)], - sizeof (*iv)); + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (key, + sizeof (*key), + /* salt / XTS */ + nonce, + sizeof (*nonce), + /* ikm */ + key_material, + key_m_len, + /* info chunks */ + /* The "salt" passed here is actually not something random, + but a protocol-specific identifier string. Thus + we pass it as a context info to the HKDF */ + salt, + strlen (salt), + NULL, + 0)); } @@ -141,67 +137,25 @@ anastasis_encrypt (const struct ANASTASIS_CRYPTO_NonceP *nonce, void **res, size_t *res_size) { - struct ANASTASIS_CRYPTO_NonceP *nonceptr; - gcry_cipher_hd_t cipher; - struct ANASTASIS_CRYPTO_SymKeyP sym_key; - struct ANASTASIS_CRYPTO_IvP iv; - int rc; - struct ANASTASIS_CRYPTO_AesTagP *tag; - char *ciphertext; - - *res_size = data_size - + sizeof (struct ANASTASIS_CRYPTO_NonceP) - + sizeof (struct ANASTASIS_CRYPTO_AesTagP); - if (*res_size <= data_size) - { - GNUNET_break (0); - return; - } - *res = GNUNET_malloc (*res_size); - if (*res_size != data_size - + sizeof (struct ANASTASIS_CRYPTO_NonceP) - + sizeof (struct ANASTASIS_CRYPTO_AesTagP)) - { - GNUNET_break (0); - return; - } - nonceptr = (struct ANASTASIS_CRYPTO_NonceP *) *res; - tag = (struct ANASTASIS_CRYPTO_AesTagP *) &nonceptr[1]; - ciphertext = (char *) &tag[1]; - memcpy (nonceptr, - nonce, - sizeof (*nonce)); - get_iv_key (key, + size_t ciphertext_size; + struct ANASTASIS_CRYPTO_SymKeyP skey; + + derive_key (key, key_len, nonce, salt, - &sym_key, - &iv); - GNUNET_assert (0 == - gcry_cipher_open (&cipher, - GCRY_CIPHER_AES256, - GCRY_CIPHER_MODE_GCM, - 0)); - rc = gcry_cipher_setkey (cipher, - &sym_key, - sizeof (sym_key)); - GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY)); - rc = gcry_cipher_setiv (cipher, - &iv, - sizeof (iv)); - GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY)); - + &skey); + ciphertext_size = crypto_secretbox_NONCEBYTES + + crypto_secretbox_MACBYTES + data_size; + *res_size = ciphertext_size; + *res = GNUNET_malloc (ciphertext_size); + memcpy (*res, nonce, crypto_secretbox_NONCEBYTES); GNUNET_assert (0 == - gcry_cipher_encrypt (cipher, - ciphertext, - data_size, - data, - data_size)); - GNUNET_assert (0 == - gcry_cipher_gettag (cipher, - tag, - sizeof (struct ANASTASIS_CRYPTO_AesTagP))); - gcry_cipher_close (cipher); + crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES, + data, + data_size, + (void *) nonce, + (void *) &skey)); } @@ -215,8 +169,9 @@ anastasis_encrypt (const struct ANASTASIS_CRYPTO_NonceP *nonce, * @param salt salt value which is used for key derivation * @param[out] res plaintext output * @param[out] res_size size of the plaintext + * @return #GNUNET_OK on success */ -static void +static enum GNUNET_GenericReturnValue anastasis_decrypt (const void *key, size_t key_len, const void *data, @@ -226,77 +181,42 @@ anastasis_decrypt (const void *key, size_t *res_size) { const struct ANASTASIS_CRYPTO_NonceP *nonce; - gcry_cipher_hd_t cipher; - const struct ANASTASIS_CRYPTO_SymKeyP sym_key; - struct ANASTASIS_CRYPTO_IvP iv; - int rc; - const struct ANASTASIS_CRYPTO_AesTagP *tag; - const char *ciphertext; - - *res_size = data_size - - sizeof (struct ANASTASIS_CRYPTO_NonceP) - - sizeof (struct ANASTASIS_CRYPTO_AesTagP); - if (*res_size >= data_size) - { - GNUNET_break (0); - return; - } - *res = GNUNET_malloc (*res_size); - if (*res_size != data_size - - sizeof (struct ANASTASIS_CRYPTO_NonceP) - - sizeof (struct ANASTASIS_CRYPTO_AesTagP)) + struct ANASTASIS_CRYPTO_SymKeyP skey; + size_t plaintext_size; + + if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES) { GNUNET_break (0); - GNUNET_free (*res); - return; + return GNUNET_SYSERR; } - - nonce = (const struct ANASTASIS_CRYPTO_NonceP *) data; - tag = (struct ANASTASIS_CRYPTO_AesTagP *) &nonce[1]; - ciphertext = (const char *) &tag[1]; - get_iv_key (key, + nonce = data; + derive_key (key, key_len, nonce, salt, - &sym_key, - &iv); - GNUNET_assert (0 == - gcry_cipher_open (&cipher, - GCRY_CIPHER_AES256, - GCRY_CIPHER_MODE_GCM, - 0)); - rc = gcry_cipher_setkey (cipher, - &sym_key, - sizeof (sym_key)); - GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY)); - - rc = gcry_cipher_setiv (cipher, - &iv, - sizeof (iv)); - GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY)); - - GNUNET_assert (0 == gcry_cipher_decrypt (cipher, - *res, - *res_size, - ciphertext, - *res_size)); - if (0 != - gcry_cipher_checktag (cipher, - tag, - sizeof (struct ANASTASIS_CRYPTO_AesTagP))) + &skey); + plaintext_size = data_size - (crypto_secretbox_NONCEBYTES + + crypto_secretbox_MACBYTES); + *res = GNUNET_malloc (plaintext_size); + *res_size = plaintext_size; + if (0 != crypto_secretbox_open_easy (*res, + data + crypto_secretbox_NONCEBYTES, + data_size - crypto_secretbox_NONCEBYTES, + (void *) nonce, + (void *) &skey)) { GNUNET_break (0); GNUNET_free (*res); - return; + return GNUNET_SYSERR; } - gcry_cipher_close (cipher); + return GNUNET_OK; } void ANASTASIS_CRYPTO_user_identifier_derive ( const json_t *id_data, - const struct ANASTASIS_CRYPTO_ProviderSaltP *server_salt, + const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt, struct ANASTASIS_CRYPTO_UserIdentifierP *id) { char *json_enc; @@ -305,7 +225,7 @@ ANASTASIS_CRYPTO_user_identifier_derive ( json_enc = json_dumps (id_data, JSON_COMPACT | JSON_SORT_KEYS); GNUNET_assert (NULL != json_enc); - GNUNET_CRYPTO_pow_hash (&server_salt->salt, + GNUNET_CRYPTO_pow_hash (&provider_salt->salt, json_enc, strlen (json_enc), &hash); @@ -321,23 +241,23 @@ ANASTASIS_CRYPTO_account_private_key_derive ( { /* priv_key = ver_secret */ if (GNUNET_YES != - GNUNET_CRYPTO_hkdf (&priv_key->priv, - sizeof (priv_key->priv), - GCRY_MD_SHA512, - GCRY_MD_SHA256, - id, - sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP), - "ver", - strlen ("ver"), - NULL, - 0)) + GNUNET_CRYPTO_kdf (&priv_key->priv, + sizeof (priv_key->priv), + /* salt / XTS */ + NULL, + 0, + /* ikm */ + id, + sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP), + /* context chunks */ + "ver", + strlen ("ver"), + NULL, + 0)) { GNUNET_break (0); return; } - /* go from ver_secret to proper private key (eddsa_d_to_a() in spec) */ - priv_key->priv.d[0] = (priv_key->priv.d[0] & 0x7f) | 0x40; - priv_key->priv.d[31] &= 0xf8; } @@ -417,9 +337,9 @@ ANASTASIS_CRYPTO_keyshare_encrypt ( sizeof (nonce)); anastasis_encrypt (&nonce, id, - sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP), + sizeof (*id), key_share, - sizeof (struct ANASTASIS_CRYPTO_KeyShareP), + sizeof (*key_share), (NULL == xsalt) ? salt : xsalt, &eks, &eks_size); @@ -444,9 +364,9 @@ ANASTASIS_CRYPTO_keyshare_decrypt ( void *ks = NULL; anastasis_decrypt (id, - sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP), + sizeof (*id), enc_key_share, - sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP), + sizeof (*enc_key_share), (NULL == xsalt) ? salt : xsalt, &ks, &ks_size); @@ -518,105 +438,113 @@ ANASTASIS_CRYPTO_policy_key_derive ( const struct ANASTASIS_CRYPTO_MasterSaltP *salt, struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key) { - GNUNET_CRYPTO_hkdf (policy_key, - sizeof (*policy_key), - GCRY_MD_SHA512, - GCRY_MD_SHA256, - key_shares, - keyshare_length * sizeof (*key_shares), - salt, - sizeof (*salt), - NULL, 0); + GNUNET_CRYPTO_kdf (policy_key, + sizeof (*policy_key), + /* salt / XTS */ + salt, + sizeof (*salt), + /* ikm */ + key_shares, + keyshare_length * sizeof (*key_shares), + /* info chunks */ + "anastasis-policy-key-derive", + strlen ("anastasis-policy-key-derive"), + NULL, 0); } -void +struct ANASTASIS_CoreSecretEncryptionResult * ANASTASIS_CRYPTO_core_secret_encrypt ( const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_keys, unsigned int policy_keys_length, const void *core_secret, - size_t core_secret_size, - void **enc_core_secret, - struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_keys) + size_t core_secret_size) { - struct GNUNET_CRYPTO_SymmetricSessionKey sk; - struct GNUNET_CRYPTO_SymmetricInitializationVector iv; struct GNUNET_HashCode master_key; + struct ANASTASIS_CoreSecretEncryptionResult *cser; + struct ANASTASIS_CRYPTO_NonceP nonce; + + cser = GNUNET_new (struct ANASTASIS_CoreSecretEncryptionResult); - *enc_core_secret = GNUNET_malloc (core_secret_size); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, &master_key, sizeof (struct GNUNET_HashCode)); - GNUNET_CRYPTO_hash_to_aes_key (&master_key, - &sk, - &iv); - GNUNET_assert (GNUNET_SYSERR != - GNUNET_CRYPTO_symmetric_encrypt (core_secret, - core_secret_size, - &sk, - &iv, - *enc_core_secret)); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &nonce, + sizeof (struct ANASTASIS_CRYPTO_NonceP)); + + anastasis_encrypt (&nonce, + &master_key, + sizeof (struct GNUNET_HashCode), + core_secret, + core_secret_size, + "cse", + &cser->enc_core_secret, + &cser->enc_core_secret_size); + + /* Allocate result arrays with NULL-termination so we don't + need to store the length to free */ + cser->enc_master_key_sizes = GNUNET_new_array (policy_keys_length + 1, + size_t); + cser->enc_master_keys = GNUNET_new_array (policy_keys_length + 1, + void *); + for (unsigned int i = 0; i < policy_keys_length; i++) { - struct GNUNET_CRYPTO_SymmetricSessionKey i_sk; - struct GNUNET_CRYPTO_SymmetricInitializationVector i_iv; - struct GNUNET_HashCode key = policy_keys[i].key; - - GNUNET_CRYPTO_hash_to_aes_key (&key, - &i_sk, - &i_iv); - GNUNET_assert ( - GNUNET_SYSERR != - GNUNET_CRYPTO_symmetric_encrypt (&master_key, - sizeof (struct GNUNET_HashCode), - &i_sk, - &i_iv, - &encrypted_master_keys[i])); + struct ANASTASIS_CRYPTO_NonceP nonce_i; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &nonce_i, + sizeof (struct ANASTASIS_CRYPTO_NonceP)); + + anastasis_encrypt (&nonce_i, + &policy_keys[i].key, + sizeof (struct GNUNET_HashCode), + &master_key, + sizeof (struct GNUNET_HashCode), + "emk", + &cser->enc_master_keys[i], + &cser->enc_master_key_sizes[i]); } + return cser; } void ANASTASIS_CRYPTO_core_secret_recover ( - const struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_key, + const void *encrypted_master_key, + size_t encrypted_master_key_size, const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key, const void *encrypted_core_secret, size_t encrypted_core_secret_size, void **core_secret, size_t *core_secret_size) { - struct GNUNET_CRYPTO_SymmetricSessionKey mk_sk; - struct GNUNET_CRYPTO_SymmetricInitializationVector mk_iv; - struct GNUNET_CRYPTO_SymmetricSessionKey core_sk; - struct GNUNET_CRYPTO_SymmetricInitializationVector core_iv; - struct GNUNET_HashCode master_key; - struct GNUNET_HashCode key = policy_key->key; + void *master_key; + size_t master_key_size; *core_secret = GNUNET_malloc (encrypted_core_secret_size); - GNUNET_CRYPTO_hash_to_aes_key (&key, - &mk_sk, - &mk_iv); - GNUNET_assert ( - GNUNET_SYSERR != - GNUNET_CRYPTO_symmetric_decrypt ( - encrypted_master_key, - sizeof (struct ANASTASIS_CRYPTO_EncryptedMasterKeyP), - &mk_sk, - &mk_iv, - &master_key)); - GNUNET_CRYPTO_hash_to_aes_key (&master_key, - &core_sk, - &core_iv); + anastasis_decrypt (&policy_key->key, + sizeof (struct GNUNET_HashCode), + encrypted_master_key, + encrypted_master_key_size, + "emk", + &master_key, + &master_key_size); + GNUNET_break (NULL != master_key); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "At %s:%d encrypted core secret is %s-%llu b\n", __FILE__, __LINE__, TALER_b2s (encrypted_core_secret, encrypted_core_secret_size), (unsigned long long) encrypted_core_secret_size); - *core_secret_size = GNUNET_CRYPTO_symmetric_decrypt (encrypted_core_secret, - encrypted_core_secret_size, - &core_sk, - &core_iv, - *core_secret); + anastasis_decrypt (master_key, + master_key_size, + encrypted_core_secret, + encrypted_core_secret_size, + "cse", + core_secret, + core_secret_size); + GNUNET_break (NULL != *core_secret); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "At %s:%d decrypted core secret is %s-%llu b\n", __FILE__, __LINE__, @@ -626,4 +554,78 @@ ANASTASIS_CRYPTO_core_secret_recover ( } +void +ANASTASIS_CRYPTO_destroy_encrypted_core_secret ( + struct ANASTASIS_CoreSecretEncryptionResult *cser) +{ + for (unsigned int i = 0; NULL != cser->enc_master_keys[i]; i++) + GNUNET_free (cser->enc_master_keys[i]); + GNUNET_free (cser->enc_master_keys); + GNUNET_free (cser->enc_master_key_sizes); + GNUNET_free (cser->enc_core_secret); + GNUNET_free (cser); +} + + +const char * +ANASTASIS_CRYPTO_uuid2s (const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid) +{ + static char uuids[7]; + char *tpk; + + tpk = GNUNET_STRINGS_data_to_string_alloc (uuid, + sizeof (*uuid)); + memcpy (uuids, + tpk, + sizeof (uuids) - 1); + GNUNET_free (tpk); + return uuids; +} + + +void +ANASTASIS_CRYPTO_recovery_metadata_encrypt ( + const struct ANASTASIS_CRYPTO_UserIdentifierP *id, + const void *meta_data, + size_t meta_data_size, + void **enc_meta_data, + size_t *enc_meta_data_size) +{ + const char *salt = "rmd"; + struct ANASTASIS_CRYPTO_NonceP nonce; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + anastasis_encrypt (&nonce, + id, + sizeof (*id), + meta_data, + meta_data_size, + salt, + enc_meta_data, + enc_meta_data_size); +} + + +enum GNUNET_GenericReturnValue +ANASTASIS_CRYPTO_recovery_metadata_decrypt ( + const struct ANASTASIS_CRYPTO_UserIdentifierP *id, + const void *enc_meta_data, + size_t enc_meta_data_size, + void **meta_data, + size_t *meta_data_size) +{ + const char *salt = "rmd"; + + return anastasis_decrypt (id, + sizeof (*id), + enc_meta_data, + enc_meta_data_size, + salt, + meta_data, + meta_data_size); +} + + /* end of anastasis_crypto.c */ diff --git a/src/util/os_installation.c b/src/util/os_installation.c index a23182e..cfcf3c3 100644 --- a/src/util/os_installation.c +++ b/src/util/os_installation.c @@ -3,7 +3,7 @@ Copyright (C) 2019, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published + 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. @@ -12,7 +12,7 @@ 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 Affero General Public License + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/src/util/pin.c b/src/util/pin.c new file mode 100644 index 0000000..0285bb0 --- /dev/null +++ b/src/util/pin.c @@ -0,0 +1,84 @@ +/* + This file is part of GNU Anastasis. + Copyright (C) 2021 Anastasis SARL + + Anastasis 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. + + Anastasis 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 Anastasis; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file anastasis/src/util/pin.c + * @brief pin conversion functions + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis_util_lib.h" + + +bool +ANASTASIS_scan_pin (const char *as, + unsigned long long *pin) +{ + char dummy; + char s[16]; + + if ( (NULL != as) && + (0 == strncasecmp ("A-", as, 2)) ) + as += 2; /* skip "A-" prefix if present */ + if (strlen (as) != 18) + return false; + if ( ('-' != as[5]) || + ('-' != as[9]) || + ('-' != as[14]) ) + return false; + GNUNET_snprintf (s, + sizeof (s), + "%.5s%.3s%.4s%.3s", + as, + &as[6], + &as[10], + &as[15]); + if (1 != sscanf (s, + "%llu%c", + pin, + &dummy)) + { + GNUNET_break (0); + return false; + } + return true; +} + + +const char * +ANASTASIS_pin2s (uint64_t pin) +{ + static char buf[22]; + char tmp[16]; + + GNUNET_assert (pin < ANASTASIS_PIN_MAX_VALUE); + GNUNET_snprintf (tmp, + sizeof (tmp), + "%015llu", + (unsigned long long) pin); + GNUNET_snprintf (buf, + sizeof (buf), + "A-%.5s-%.3s-%.4s-%.3s", + tmp, + &tmp[5], + &tmp[8], + &tmp[12]); + return buf; +} diff --git a/src/util/test_anastasis_crypto.c b/src/util/test_anastasis_crypto.c index b435bea..6132e35 100644 --- a/src/util/test_anastasis_crypto.c +++ b/src/util/test_anastasis_crypto.c @@ -3,16 +3,16 @@ Copyright (C) 2014-2020 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as + 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. Anastasis 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. + GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ @@ -41,11 +41,11 @@ test_user_identifier_derive (void) struct ANASTASIS_CRYPTO_UserIdentifierP id_1; struct ANASTASIS_CRYPTO_UserIdentifierP id_2; struct ANASTASIS_CRYPTO_UserIdentifierP id_3; - struct ANASTASIS_CRYPTO_ProviderSaltP server_salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; char *salt_str = "Server-Salt-Test"; - GNUNET_memcpy (&server_salt, + GNUNET_memcpy (&provider_salt, salt_str, strlen (salt_str)); // sample data 1 @@ -59,13 +59,13 @@ test_user_identifier_derive (void) json_object_set_new (id_data_3, "arg1", json_string ("Hallo2")); ANASTASIS_CRYPTO_user_identifier_derive (id_data_1, - &server_salt, + &provider_salt, &id_1); ANASTASIS_CRYPTO_user_identifier_derive (id_data_2, - &server_salt, + &provider_salt, &id_2); ANASTASIS_CRYPTO_user_identifier_derive (id_data_3, - &server_salt, + &provider_salt, &id_3); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "UserIdentifier_1: %s\n", @@ -97,19 +97,19 @@ test_recovery_document (void) void *plaintext; size_t size_plaintext; struct ANASTASIS_CRYPTO_UserIdentifierP id; - struct ANASTASIS_CRYPTO_ProviderSaltP server_salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; int ret; json_t *id_data = json_object (); const char *test = "TEST_ERD"; char *salt_str = "Server-Salt-Test"; - GNUNET_memcpy (&server_salt, + GNUNET_memcpy (&provider_salt, salt_str, strlen (salt_str)); json_object_set_new (id_data, "arg1", json_string ("ID_DATA")); ANASTASIS_CRYPTO_user_identifier_derive (id_data, - &server_salt, + &provider_salt, &id); ANASTASIS_CRYPTO_recovery_document_encrypt (&id, test, @@ -216,11 +216,9 @@ test_core_secret (void) { const char *test = "TEST_CORE_SECRET"; const char *test_wrong = "TEST_CORE_WRONG"; - void *enc_core_secret; unsigned int policy_keys_length = 5; struct ANASTASIS_CRYPTO_MasterSaltP salt; - struct ANASTASIS_CRYPTO_EncryptedMasterKeyP - encrypted_master_keys[policy_keys_length]; + struct ANASTASIS_CoreSecretEncryptionResult *cser; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &salt, @@ -258,14 +256,10 @@ test_core_secret (void) TALER_b2s (test, strlen (test))); // test encryption of core_secret - ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys, - policy_keys_length, - test, - strlen (test), - &enc_core_secret, - (struct - ANASTASIS_CRYPTO_EncryptedMasterKeyP *) - &encrypted_master_keys); + cser = ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys, + policy_keys_length, + test, + strlen (test)); // test recover of core secret for (unsigned int k = 0; k < policy_keys_length; k++) @@ -273,10 +267,11 @@ test_core_secret (void) void *dec_core_secret; size_t core_secret_size; - ANASTASIS_CRYPTO_core_secret_recover (&encrypted_master_keys[k], + ANASTASIS_CRYPTO_core_secret_recover (cser->enc_master_keys[k], + cser->enc_master_key_sizes[k], &policy_keys[k], - enc_core_secret, - strlen (test), + cser->enc_core_secret, + cser->enc_core_secret_size, &dec_core_secret, &core_secret_size); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -291,7 +286,7 @@ test_core_secret (void) test))); GNUNET_free (dec_core_secret); } - GNUNET_free (enc_core_secret); + ANASTASIS_CRYPTO_destroy_encrypted_core_secret (cser); return 0; } @@ -301,17 +296,17 @@ test_public_key_derive (void) { struct ANASTASIS_CRYPTO_UserIdentifierP id; struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key; - struct ANASTASIS_CRYPTO_ProviderSaltP server_salt; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; json_t *id_data = json_object (); const char *salt_str = "Server-Salt-Test"; - GNUNET_memcpy (&server_salt, + GNUNET_memcpy (&provider_salt, salt_str, strlen (salt_str)); json_object_set_new (id_data, "arg1", json_string ("ID_DATA")); ANASTASIS_CRYPTO_user_identifier_derive (id_data, - &server_salt, + &provider_salt, &id); ANASTASIS_CRYPTO_account_public_key_derive (&id, |