commit c88681f75eec657d7a54d62e5ac32a23392c4b3b
parent 0922dcdf0cf7bb584e78e6ee2c038032a4b27fd2
Author: Christian Grothoff <christian@grothoff.org>
Date: Fri, 5 Sep 2025 18:36:44 +0200
-debugging MFA logic (WiP)
Diffstat:
9 files changed, 140 insertions(+), 20 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_mfa.c b/src/backend/taler-merchant-httpd_mfa.c
@@ -78,7 +78,7 @@ TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc,
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (dash + 1,
- strlen (dash) + 1,
+ strlen (dash + 1),
h_body,
sizeof (*h_body)))
{
@@ -242,6 +242,7 @@ mfa_challenge_start (
struct TALER_MERCHANT_MFA_BodySalt salt;
struct TALER_MERCHANT_MFA_BodyHash h_body;
uint64_t challenge_serial;
+ char *code;
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&salt,
@@ -249,17 +250,23 @@ mfa_challenge_start (
TALER_MERCHANT_mfa_body_hash (hc->request_body,
&salt,
&h_body);
+ GNUNET_asprintf (&code,
+ "%llu",
+ (unsigned long long)
+ GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ 1000 * 1000 * 100));
qs = TMH_db->create_mfa_challenge (TMH_db->cls,
hc->instance->settings.id,
op,
&h_body,
&salt,
- NULL,
+ code,
expiration_date,
GNUNET_TIME_UNIT_ZERO_ABS,
channel,
required_address,
&challenge_serial);
+ GNUNET_free (code);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID.c b/src/backend/taler-merchant-httpd_post-challenge-ID.c
@@ -539,7 +539,7 @@ phase_lookup (struct MfaState *mfa)
NULL);
return;
}
- if (! GNUNET_TIME_absolute_is_future (retransmission_date))
+ if (GNUNET_TIME_absolute_is_future (retransmission_date))
{
/* too early to try again */
respond_with_error (mfa,
diff --git a/src/backenddb/merchant-0023.sql b/src/backenddb/merchant-0023.sql
@@ -37,7 +37,7 @@ CREATE TYPE op_enum
CREATE TABLE tan_challenges
(challenge_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE
- ,h_body BYTEA NOT NULL CHECK (LENGTH(h_body)=64)
+ ,h_body BYTEA NOT NULL CHECK (LENGTH(h_body)=32)
,salt BYTEA NOT NULL CHECK (LENGTH(salt)=16)
,op op_enum NOT NULL
,code TEXT NOT NULL
diff --git a/src/backenddb/pg_lookup_mfa_challenge.c b/src/backenddb/pg_lookup_mfa_challenge.c
@@ -49,13 +49,16 @@ TMH_PG_lookup_mfa_challenge (
};
char *op_str;
char *chan_str;
+ bool no_conf;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("op",
&op_str),
GNUNET_PQ_result_spec_auto_from_type ("salt",
salt),
- GNUNET_PQ_result_spec_absolute_time ("confirmation_date",
- confirmation_date),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_absolute_time ("confirmation_date",
+ confirmation_date),
+ &no_conf),
GNUNET_PQ_result_spec_absolute_time ("retransmission_date",
retransmission_date),
GNUNET_PQ_result_spec_uint32 ("retry_counter",
@@ -83,8 +86,8 @@ TMH_PG_lookup_mfa_challenge (
" (SELECT merchant_serial"
" FROM merchant_instances"
" WHERE merchant_id = $1)"
- " AND (tc.challenge_id = $2)"
- " AND (tc.h_body = $3)"
+ " AND (challenge_id = $2)"
+ " AND (h_body = $3)"
" AND (expiration_date / 1000 / 1000"
" > EXTRACT(EPOCH FROM NOW()"
" AT TIME ZONE 'UTC')::bigint)");
@@ -98,6 +101,8 @@ TMH_PG_lookup_mfa_challenge (
rs);
if (qs <= 0)
return qs;
+ if (no_conf)
+ *confirmation_date = GNUNET_TIME_UNIT_FOREVER_ABS;
*tan_channel = TALER_MERCHANT_MFA_channel_from_string (chan_str);
*op = TALER_MERCHANT_MFA_co_from_string (op_str);
GNUNET_free (chan_str);
diff --git a/src/backenddb/pg_solve_mfa_challenge.c b/src/backenddb/pg_solve_mfa_challenge.c
@@ -65,7 +65,7 @@ TMH_PG_solve_mfa_challenge (
"SELECT"
" out_solved"
" ,out_retry_counter"
- " FROM merchant_do_insert_issued_token"
+ " FROM merchant_do_solve_mfa_challenge"
" ($1, $2, $3, $4);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"solve_mfa_challenge",
diff --git a/src/backenddb/pg_solve_mfa_challenge.sql b/src/backenddb/pg_solve_mfa_challenge.sql
@@ -65,11 +65,11 @@ BEGIN
-- Check if already solved before
IF my_confirmation_date IS NOT NULL
THEN
- out_solved := TRUE;
+ out_solved = TRUE;
RETURN;
END IF;
- IF (0 == out_retry_counter)
+ IF (0 = out_retry_counter)
THEN
out_solved = FALSE;
RETURN;
diff --git a/src/testing/test_merchant_mfa.conf b/src/testing/test_merchant_mfa.conf
@@ -14,10 +14,10 @@ PORT = 9966
SERVE = tcp
DB = postgres
-HELPER_SMS = test_sms_helper.sh
-HELPER_EMAIL = test_email_helper.sh
+HELPER_SMS = ./test_sms_helper.sh
+HELPER_EMAIL = ./test_email_helper.sh
MANDATORY_TAN_CHANNELS = sms email
-
+ENABLE_SELF_PROVISIONING = YES
# This specifies which database the postgres backend uses.
[merchantdb-postgres]
diff --git a/src/testing/test_merchant_mfa.sh b/src/testing/test_merchant_mfa.sh
@@ -22,9 +22,9 @@ set -eu
# Launch system.
-setup \
- -c "test_merchant_mfa.conf" \
- -m
+#setup \
+# -c "test_merchant_mfa.conf" \
+# -m
LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
echo -n "Configuring a merchant admin instance ..."
@@ -32,7 +32,7 @@ echo -n "Configuring a merchant admin instance ..."
STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer secret-token:super_secret' \
http://localhost:9966/management/instances \
- -d '{"auth":{"method":"external"},"id":"admin","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"external"},"id":"admin","name":"default","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
@@ -56,6 +56,114 @@ then
exit_fail "Expected 200 OK. Got: $STATUS"
fi
+echo -n "Self-provision instance ..."
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ http://localhost:9966/instances \
+ -d '{"auth":{"method":"external"},"id":"self","name":"default","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "202" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 202 Accepted. Got: $STATUS"
+fi
+
+C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE")
+C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE")
+
+echo -n "Requesting challenge $C1 "
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ "http://localhost:9966/challenge/$C1" \
+ -d '{}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 204 OK. Got: $STATUS"
+fi
+echo "OK"
+
+TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1)
+ADDR=$(cat /tmp/test-merchant-sms-address.txt)
+
+if [ "$ADDR" != "1234" ]
+then
+ exit_fail "Expected address '1234'. Got: $ADDR"
+fi
+
+echo -n "Sending challenge $C1 solution "
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ "http://localhost:9966/challenge/$C1/confirm" \
+ -d '{"tan":"'"$TAN"'"}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 204 OK. Got: $STATUS"
+fi
+echo "OK"
+
+
+echo -n "Requesting challenge $C2 "
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ "http://localhost:9966/challenge/$C2" \
+ -d '{}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 204 OK. Got: $STATUS"
+fi
+echo "OK"
+
+TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1)
+
+echo -n "Sending challenge $C2 solution "
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ "http://localhost:9966/challenge/$C2/confirm" \
+ -d '{"tan":"'"$TAN"'"}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 204 OK. Got: $STATUS"
+fi
+echo "OK"
+
+
+
+echo -n "Retrying instance creation "
+
+STATUS=$(curl \
+ -H "Content-Type: application/json" \
+ -H "Taler-Challenge-Ids: $C1,$C2" \
+ -X POST \
+ http://localhost:9966/instances \
+ -d '{"auth":{"method":"external"},"id":"self","name":"default","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "204" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 204 OK. Got: $STATUS"
+fi
+
+
STATUS=$(curl -H "Content-Type: application/json" -X POST \
-H 'Authorization: Bearer secret-token:super_secret' \
http://localhost:9966/private/accounts \
diff --git a/src/util/mfa.c b/src/util/mfa.c
@@ -68,7 +68,7 @@ TALER_MERCHANT_MFA_co_from_string (const char *str)
{
if (NULL == str)
return TALER_MERCHANT_MFA_CO_NONE;
- for (unsigned int i = 0;
+ for (unsigned int i = 1;
i < sizeof (co_strings) / sizeof (co_strings[0]);
i++)
{
@@ -100,7 +100,7 @@ TALER_MERCHANT_MFA_channel_from_string (const char *str)
{
if (NULL == str)
return TALER_MERCHANT_MFA_CHANNEL_NONE;
- for (unsigned int i = 0;
+ for (unsigned int i = 1;
i < sizeof (channel_strings) / sizeof (channel_strings[0]);
i++)
{