summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/authorization/Makefile.am31
-rwxr-xr-xsrc/authorization/anastasis-authorization-email.sh1
-rwxr-xr-xsrc/authorization/anastasis-authorization-post.sh196
-rwxr-xr-xsrc/authorization/anastasis-authorization-sms.sh31
-rw-r--r--src/authorization/anastasis_authorization_plugin.c2
-rw-r--r--src/authorization/anastasis_authorization_plugin_email.c76
-rw-r--r--src/authorization/anastasis_authorization_plugin_file.c32
-rw-r--r--src/authorization/anastasis_authorization_plugin_iban.c281
-rw-r--r--src/authorization/anastasis_authorization_plugin_post.c83
-rw-r--r--src/authorization/anastasis_authorization_plugin_sms.c73
-rw-r--r--src/authorization/anastasis_authorization_plugin_totp.c381
-rw-r--r--src/authorization/authorization-email-messages.json4
-rw-r--r--src/authorization/authorization-post-messages.json2
-rw-r--r--src/authorization/iban.h2
-rw-r--r--src/authorization/libanastasiseufin/lae_common.h2
-rw-r--r--src/authorization/libanastasiseufin/lae_credit.c8
-rwxr-xr-xsrc/authorization/test-post.sh12
-rw-r--r--src/backend/Makefile.am11
-rw-r--r--src/backend/anastasis-httpd.c152
-rw-r--r--src/backend/anastasis-httpd.h9
-rw-r--r--src/backend/anastasis-httpd_config.c115
-rw-r--r--src/backend/anastasis-httpd_policy-meta.c192
-rw-r--r--src/backend/anastasis-httpd_policy-meta.h41
-rw-r--r--src/backend/anastasis-httpd_policy-upload.c (renamed from src/backend/anastasis-httpd_policy_upload.c)222
-rw-r--r--src/backend/anastasis-httpd_policy.c36
-rw-r--r--src/backend/anastasis-httpd_policy.h2
-rw-r--r--src/backend/anastasis-httpd_terms.c23
-rw-r--r--src/backend/anastasis-httpd_terms.h2
-rw-r--r--src/backend/anastasis-httpd_truth-challenge.c (renamed from src/backend/anastasis-httpd_truth.c)801
-rw-r--r--src/backend/anastasis-httpd_truth-solve.c1474
-rw-r--r--src/backend/anastasis-httpd_truth-upload.c (renamed from src/backend/anastasis-httpd_truth_upload.c)249
-rw-r--r--src/backend/anastasis-httpd_truth.h59
-rw-r--r--src/backend/anastasis.conf6
-rw-r--r--src/cli/.gitignore7
-rw-r--r--src/cli/Makefile.am28
-rw-r--r--src/cli/anastasis-cli-discover.c261
-rw-r--r--src/cli/anastasis-cli-redux.c43
-rw-r--r--src/cli/resources/00-backup.json7
-rw-r--r--src/cli/resources/00-recovery.json3
-rw-r--r--src/cli/resources/01-backup.json27
-rw-r--r--src/cli/resources/01-recovery.json3
-rw-r--r--src/cli/resources/02-backup.json35
-rw-r--r--src/cli/resources/02-recovery.json9
-rw-r--r--src/cli/resources/03-backup.json45
-rw-r--r--src/cli/resources/04-backup.json44
-rw-r--r--src/cli/resources/05-backup.json13
-rw-r--r--src/cli/resources/06-backup.json13
-rwxr-xr-xsrc/cli/setup.sh72
-rw-r--r--src/cli/test_anastasis_reducer_1.conf2
-rw-r--r--src/cli/test_anastasis_reducer_2.conf2
-rw-r--r--src/cli/test_anastasis_reducer_3.conf2
-rw-r--r--src/cli/test_anastasis_reducer_4.conf2
-rwxr-xr-xsrc/cli/test_anastasis_reducer_backup_enter_user_attributes.sh10
-rwxr-xr-xsrc/cli/test_anastasis_reducer_done_authentication.sh4
-rwxr-xr-xsrc/cli/test_anastasis_reducer_enter_secret.sh438
-rw-r--r--src/cli/test_anastasis_reducer_free_1.conf10
-rw-r--r--src/cli/test_anastasis_reducer_free_2.conf10
-rw-r--r--src/cli/test_anastasis_reducer_free_3.conf10
-rw-r--r--src/cli/test_anastasis_reducer_free_4.conf10
-rwxr-xr-xsrc/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh500
-rwxr-xr-xsrc/cli/test_anastasis_reducer_recovery_hanging.sh376
-rwxr-xr-xsrc/cli/test_anastasis_reducer_recovery_no_pay.sh351
-rwxr-xr-xsrc/cli/test_anastasis_reducer_select_continent.sh14
-rwxr-xr-xsrc/cli/test_anastasis_reducer_select_country.sh18
-rw-r--r--src/cli/test_free_reducer.conf2
-rwxr-xr-xsrc/cli/test_iban.sh363
-rw-r--r--src/cli/test_reducer.conf76
-rw-r--r--src/cli/test_reducer_free.conf210
-rw-r--r--src/cli/test_reducer_home/.local/share/taler/exchange-offline/master.priv1
-rw-r--r--src/include/anastasis.h370
-rw-r--r--src/include/anastasis_authorization_lib.h2
-rw-r--r--src/include/anastasis_authorization_plugin.h82
-rw-r--r--src/include/anastasis_crypto_lib.h171
-rw-r--r--src/include/anastasis_database_lib.h2
-rw-r--r--src/include/anastasis_database_plugin.h69
-rw-r--r--src/include/anastasis_eufin_lib.h2
-rw-r--r--src/include/anastasis_redux.h113
-rw-r--r--src/include/anastasis_service.h716
-rw-r--r--src/include/anastasis_testing_lib.h484
-rw-r--r--src/include/anastasis_util_lib.h39
-rw-r--r--src/include/gettext.h4
-rw-r--r--src/include/platform.h214
-rw-r--r--src/lib/Makefile.am1
-rw-r--r--src/lib/anastasis_backup.c113
-rw-r--r--src/lib/anastasis_meta.c178
-rw-r--r--src/lib/anastasis_recovery.c721
-rw-r--r--src/reducer/Makefile.am5
-rw-r--r--src/reducer/anastasis_api_backup_redux.c658
-rw-r--r--src/reducer/anastasis_api_discovery.c549
-rw-r--r--src/reducer/anastasis_api_providers.c300
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c1863
-rw-r--r--src/reducer/anastasis_api_redux.c735
-rw-r--r--src/reducer/anastasis_api_redux.h68
-rw-r--r--src/reducer/validation_CH_AHV.c6
-rw-r--r--src/reducer/validation_CZ_BN.c6
-rw-r--r--src/reducer/validation_DE_SVN.c6
-rw-r--r--src/reducer/validation_DE_TIN.c6
-rw-r--r--src/reducer/validation_ES_DNI.c184
-rw-r--r--src/reducer/validation_FR_INSEE.c66
-rw-r--r--src/reducer/validation_IN_AADHAR.c6
-rw-r--r--src/reducer/validation_IT_CF.c6
-rw-r--r--src/reducer/validation_NL_BSN.c57
-rw-r--r--src/reducer/validation_XX_SQUARE.c6
-rw-r--r--src/reducer/validation_XY_PRIME.c6
-rw-r--r--src/restclient/Makefile.am10
-rw-r--r--src/restclient/anastasis_api_config.c134
-rw-r--r--src/restclient/anastasis_api_curl_defaults.c19
-rw-r--r--src/restclient/anastasis_api_curl_defaults.h6
-rw-r--r--src/restclient/anastasis_api_keyshare_lookup.c67
-rw-r--r--src/restclient/anastasis_api_policy_lookup.c29
-rw-r--r--src/restclient/anastasis_api_policy_meta_lookup.c272
-rw-r--r--src/restclient/anastasis_api_policy_store.c53
-rw-r--r--src/restclient/anastasis_api_truth_challenge.c456
-rw-r--r--src/restclient/anastasis_api_truth_solve.c437
-rw-r--r--src/restclient/anastasis_api_truth_store.c18
-rw-r--r--src/stasis/Makefile.am15
-rw-r--r--src/stasis/anastasis_db_plugin.c2
-rw-r--r--src/stasis/drop.sql (renamed from src/stasis/drop0001.sql)25
-rw-r--r--src/stasis/plugin_anastasis_postgres.c617
-rw-r--r--src/stasis/stasis-0001.sql60
-rw-r--r--src/stasis/test_anastasis_db.c14
-rw-r--r--src/stasis/versioning.sql (renamed from src/stasis/stasis-0000.sql)3
-rw-r--r--src/testing/.gitignore5
-rw-r--r--src/testing/Makefile.am25
-rw-r--r--src/testing/test_anastasis.c186
-rw-r--r--src/testing/test_anastasis_api.c200
-rw-r--r--src/testing/test_anastasis_api.conf170
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json3
-rw-r--r--src/testing/test_anastasis_api_home/taler/exchange-offline/master.priv (renamed from src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv)0
-rw-r--r--src/testing/test_anastasis_api_home/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/testing_api_cmd_config.c46
-rw-r--r--src/testing/testing_api_cmd_policy_lookup.c37
-rw-r--r--src/testing/testing_api_cmd_policy_store.c40
-rw-r--r--src/testing/testing_api_cmd_truth_challenge.c368
-rw-r--r--src/testing/testing_api_cmd_truth_solve.c (renamed from src/testing/testing_api_cmd_keyshare_lookup.c)240
-rw-r--r--src/testing/testing_api_cmd_truth_store.c43
-rw-r--r--src/testing/testing_api_helpers.c6
-rw-r--r--src/testing/testing_api_trait_account_priv.c58
-rw-r--r--src/testing/testing_api_trait_account_pub.c58
-rw-r--r--src/testing/testing_api_trait_code.c59
-rw-r--r--src/testing/testing_api_trait_eks.c58
-rw-r--r--src/testing/testing_api_trait_hash.c57
-rw-r--r--src/testing/testing_api_trait_payment_secret.c59
-rw-r--r--src/testing/testing_api_trait_salt.c60
-rw-r--r--src/testing/testing_api_trait_truth_key.c58
-rw-r--r--src/testing/testing_api_trait_truth_uuid.c61
-rw-r--r--src/testing/testing_api_traits.c36
-rw-r--r--src/testing/testing_cmd_challenge_answer.c280
-rw-r--r--src/testing/testing_cmd_policy_create.c17
-rw-r--r--src/testing/testing_cmd_recover_secret.c64
-rw-r--r--src/testing/testing_cmd_secret_share.c69
-rw-r--r--src/testing/testing_cmd_truth_upload.c57
-rw-r--r--src/testing/testing_trait_challenge.c57
-rw-r--r--src/testing/testing_trait_core_secret.c59
-rw-r--r--src/testing/testing_trait_policy.c58
-rw-r--r--src/testing/testing_trait_truth.c58
-rw-r--r--src/util/.gitignore1
-rw-r--r--src/util/Makefile.am21
-rw-r--r--src/util/anastasis-config.c6
-rw-r--r--src/util/anastasis-config.in5
-rw-r--r--src/util/anastasis-crypto-tvg.c601
-rw-r--r--src/util/anastasis_crypto.c470
-rw-r--r--src/util/os_installation.c4
-rw-r--r--src/util/pin.c84
-rw-r--r--src/util/test_anastasis_crypto.c53
165 files changed, 14521 insertions, 7060 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 c3e858c..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,32 +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
+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 \
@@ -225,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"
@@ -233,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}"
@@ -275,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
@@ -297,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 \
@@ -314,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",
@@ -325,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"
+
+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 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"
+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 '
{
@@ -393,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 (&notify_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",
- &regexp)),
+ &regexp),
+ NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("validation-logic",
- &reglog)),
+ &reglog),
+ 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",
- &current,
- &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,