diff options
Diffstat (limited to 'src/authorization')
17 files changed, 922 insertions, 295 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" |