aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-09-28 15:50:28 +0200
committerChristian Grothoff <christian@grothoff.org>2021-09-28 15:50:28 +0200
commit435950ee10fc3d58f7ff992a2c2a2a3f73efa806 (patch)
treec0f900b8ff04e4f5c9d9ec50d0657a76ea41372d
parent3c3984cdcb88d5d3eb568d7e9647a2f2c473c0bc (diff)
downloadanastasis-435950ee10fc3d58f7ff992a2c2a2a3f73efa806.tar.gz
anastasis-435950ee10fc3d58f7ff992a2c2a2a3f73efa806.zip
theoretically, this completes the TOTP implementation, alas completely untested
-rw-r--r--src/authorization/anastasis_authorization_plugin_totp.c47
-rw-r--r--src/backend/anastasis-httpd_truth.c374
-rw-r--r--src/include/anastasis_database_plugin.h2
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c9
4 files changed, 300 insertions, 132 deletions
diff --git a/src/authorization/anastasis_authorization_plugin_totp.c b/src/authorization/anastasis_authorization_plugin_totp.c
index 6fcdd39..ee1ab3f 100644
--- a/src/authorization/anastasis_authorization_plugin_totp.c
+++ b/src/authorization/anastasis_authorization_plugin_totp.c
@@ -59,14 +59,14 @@ struct ANASTASIS_AUTHORIZATION_State
59 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; 59 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
60 60
61 /** 61 /**
62 * Our context. 62 * Was the challenge satisfied?
63 */ 63 */
64 const struct ANASTASIS_AuthorizationContext *ac; 64 struct GNUNET_HashCode valid_replies[TIME_INTERVAL_RANGE * 2 + 1];
65 65
66 /** 66 /**
67 * Was the challenge satisfied? 67 * Our context.
68 */ 68 */
69 bool ok; 69 const struct ANASTASIS_AuthorizationContext *ac;
70 70
71}; 71};
72 72
@@ -212,9 +212,9 @@ compute_totp (int time_off,
212 * @param trigger_cls closure for @a trigger 212 * @param trigger_cls closure for @a trigger
213 * @param truth_uuid Identifier of the challenge, to be (if possible) included in the 213 * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
214 * interaction with the user 214 * interaction with the user
215 * @param code set to secret code that the user provided to satisfy the challenge in 215 * @param code always 0 (direct validation, backend does
216 * the main anastasis protocol 216 * not generate a code in this mode)
217 * @param data input to validate (i.e. the shared secret) 217 * @param data truth for input to validate (i.e. the shared secret)
218 * @param data_length number of bytes in @a data 218 * @param data_length number of bytes in @a data
219 * @return state to track progress on the authorization operation, NULL on failure 219 * @return state to track progress on the authorization operation, NULL on failure
220 */ 220 */
@@ -230,7 +230,9 @@ totp_start (void *cls,
230 const struct ANASTASIS_AuthorizationContext *ac = cls; 230 const struct ANASTASIS_AuthorizationContext *ac = cls;
231 struct ANASTASIS_AUTHORIZATION_State *as; 231 struct ANASTASIS_AUTHORIZATION_State *as;
232 uint64_t want; 232 uint64_t want;
233 unsigned int off = 0;
233 234
235 GNUNET_assert (0 == code);
234 as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State); 236 as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
235 as->ac = ac; 237 as->ac = ac;
236 as->truth_uuid = *truth_uuid; 238 as->truth_uuid = *truth_uuid;
@@ -241,8 +243,8 @@ totp_start (void *cls,
241 want = compute_totp (i, 243 want = compute_totp (i,
242 data, 244 data,
243 data_length); 245 data_length);
244 if (code == want) 246 ANASTASIS_hash_answer (want,
245 as->ok = true; 247 &as->valid_replies[off++]);
246 } 248 }
247 return as; 249 return as;
248} 250}
@@ -264,9 +266,32 @@ totp_process (struct ANASTASIS_AUTHORIZATION_State *as,
264 MHD_RESULT mres; 266 MHD_RESULT mres;
265 const char *mime; 267 const char *mime;
266 const char *lang; 268 const char *lang;
269 const char *challenge_response_s;
270 struct GNUNET_HashCode challenge_response;
271
272 challenge_response_s = MHD_lookup_connection_value (connection,
273 MHD_GET_ARGUMENT_KIND,
274 "response");
275 if ( (NULL == challenge_response_s) ||
276 (GNUNET_OK !=
277 GNUNET_CRYPTO_hash_from_string (challenge_response_s,
278 &challenge_response)) )
279 {
280 GNUNET_break_op (0);
281 mres = TALER_MHD_reply_with_error (connection,
282 MHD_HTTP_BAD_REQUEST,
283 TALER_EC_GENERIC_PARAMETER_MALFORMED,
284 "response");
285 if (MHD_YES != mres)
286 return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
287 return ANASTASIS_AUTHORIZATION_RES_FAILED;
267 288
268 if (as->ok) 289 }
269 return ANASTASIS_AUTHORIZATION_RES_FINISHED; 290 for (unsigned int i = 0; i<=TIME_INTERVAL_RANGE * 2; i++)
291 if (0 ==
292 GNUNET_memcmp (&challenge_response,
293 &as->valid_replies[i]))
294 return ANASTASIS_AUTHORIZATION_RES_FINISHED;
270 mime = MHD_lookup_connection_value (connection, 295 mime = MHD_lookup_connection_value (connection,
271 MHD_HEADER_KIND, 296 MHD_HEADER_KIND,
272 MHD_HTTP_HEADER_ACCEPT); 297 MHD_HTTP_HEADER_ACCEPT);
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c
index 4dd3ddc..aedd0a2 100644
--- a/src/backend/anastasis-httpd_truth.c
+++ b/src/backend/anastasis-httpd_truth.c
@@ -882,6 +882,38 @@ return_key_share (
882 882
883 883
884/** 884/**
885 * Mark @a gc as suspended and update the respective
886 * data structures and jobs.
887 *
888 * @param[in,out] gc context of the suspended operation
889 */
890static void
891gc_suspended (struct GetContext *gc)
892{
893 gc->suspended = true;
894 if (NULL == AH_to_heap)
895 AH_to_heap = GNUNET_CONTAINER_heap_create (
896 GNUNET_CONTAINER_HEAP_ORDER_MIN);
897 gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap,
898 gc,
899 gc->timeout.abs_value_us);
900 if (NULL != to_task)
901 {
902 GNUNET_SCHEDULER_cancel (to_task);
903 to_task = NULL;
904 }
905 {
906 struct GetContext *rn;
907
908 rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
909 to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
910 &do_timeout,
911 NULL);
912 }
913}
914
915
916/**
885 * Run the authorization method-specific 'process' function and continue 917 * Run the authorization method-specific 'process' function and continue
886 * based on its result with generating an HTTP response. 918 * based on its result with generating an HTTP response.
887 * 919 *
@@ -923,26 +955,7 @@ run_authorization_process (struct MHD_Connection *connection,
923 return MHD_YES; 955 return MHD_YES;
924 case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: 956 case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
925 /* connection was suspended */ 957 /* connection was suspended */
926 gc->suspended = true; 958 gc_suspended (gc);
927 if (NULL == AH_to_heap)
928 AH_to_heap = GNUNET_CONTAINER_heap_create (
929 GNUNET_CONTAINER_HEAP_ORDER_MIN);
930 gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap,
931 gc,
932 gc->timeout.abs_value_us);
933 if (NULL != to_task)
934 {
935 GNUNET_SCHEDULER_cancel (to_task);
936 to_task = NULL;
937 }
938 {
939 struct GetContext *rn;
940
941 rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
942 to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
943 &do_timeout,
944 NULL);
945 }
946 return MHD_YES; 959 return MHD_YES;
947 case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: 960 case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
948 /* Challenge sent successfully */ 961 /* Challenge sent successfully */
@@ -978,6 +991,212 @@ run_authorization_process (struct MHD_Connection *connection,
978} 991}
979 992
980 993
994/**
995 * Use the database to rate-limit queries to the
996 * authentication procedure, but without actually
997 * storing 'real' challenge codes.
998 *
999 * @param[in,out] gc context to rate limit requests for
1000 * @return #GNUNET_OK if rate-limiting passes,
1001 * #GNUNET_NO if a reply was sent (rate limited)
1002 * #GNUNET_SYSERR if we failed and no reply
1003 * was queued
1004 */
1005static enum GNUNET_GenericReturnValue
1006rate_limit (struct GetContext *gc)
1007{
1008 enum GNUNET_DB_QueryStatus qs;
1009 struct GNUNET_TIME_Absolute rt;
1010 uint64_t code;
1011 enum ANASTASIS_DB_CodeStatus cs;
1012 struct GNUNET_HashCode hc;
1013 bool satisfied;
1014 uint64_t dummy;
1015
1016 rt = GNUNET_TIME_UNIT_FOREVER_ABS;
1017 qs = db->create_challenge_code (db->cls,
1018 &gc->truth_uuid,
1019 MAX_QUESTION_FREQ,
1020 GNUNET_TIME_UNIT_HOURS,
1021 INITIAL_RETRY_COUNTER,
1022 &rt,
1023 &code);
1024 if (0 > qs)
1025 {
1026 GNUNET_break (0 < qs);
1027 return (MHD_YES ==
1028 TALER_MHD_reply_with_error (gc->connection,
1029 MHD_HTTP_INTERNAL_SERVER_ERROR,
1030 TALER_EC_GENERIC_DB_FETCH_FAILED,
1031 "create_challenge_code (for rate limiting)"))
1032 ? GNUNET_NO
1033 : GNUNET_SYSERR;
1034 }
1035 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1036 {
1037 return (MHD_YES ==
1038 TALER_MHD_reply_with_error (gc->connection,
1039 MHD_HTTP_TOO_MANY_REQUESTS,
1040 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1041 NULL))
1042 ? GNUNET_NO
1043 : GNUNET_SYSERR;
1044 }
1045 /* decrement trial counter */
1046 ANASTASIS_hash_answer (code + 1, /* always use wrong answer */
1047 &hc);
1048 cs = db->verify_challenge_code (db->cls,
1049 &gc->truth_uuid,
1050 &hc,
1051 &dummy,
1052 &satisfied);
1053 switch (cs)
1054 {
1055 case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
1056 /* good, what we wanted */
1057 return GNUNET_OK;
1058 case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
1059 case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
1060 GNUNET_break (0);
1061 return (MHD_YES ==
1062 TALER_MHD_reply_with_error (gc->connection,
1063 MHD_HTTP_INTERNAL_SERVER_ERROR,
1064 TALER_EC_GENERIC_DB_FETCH_FAILED,
1065 "verify_challenge_code"))
1066 ? GNUNET_NO
1067 : GNUNET_SYSERR;
1068 case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
1069 return (MHD_YES ==
1070 TALER_MHD_reply_with_error (gc->connection,
1071 MHD_HTTP_TOO_MANY_REQUESTS,
1072 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1073 NULL))
1074 ? GNUNET_NO
1075 : GNUNET_SYSERR;
1076 case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
1077 /* this should be impossible, we used code+1 */
1078 GNUNET_assert (0);
1079 }
1080 return GNUNET_SYSERR;
1081}
1082
1083
1084/**
1085 * Handle special case of a security question where we do not
1086 * generate a code. Rate limits answers against brute forcing.
1087 *
1088 * @param[in,out] gc request to handle
1089 * @param decrypted_truth hash to check against
1090 * @param decrypted_truth_size number of bytes in @a decrypted_truth
1091 * @return MHD status code
1092 */
1093static MHD_RESULT
1094handle_security_question (struct GetContext *gc,
1095 const void *decrypted_truth,
1096 size_t decrypted_truth_size)
1097{
1098 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1099 "Handling security question challenge\n");
1100 if (! gc->have_response)
1101 {
1102 return TALER_MHD_reply_with_error (gc->connection,
1103 MHD_HTTP_FORBIDDEN,
1104 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
1105 NULL);
1106 }
1107 /* rate limit */
1108 {
1109 enum GNUNET_GenericReturnValue ret;
1110
1111 ret = rate_limit (gc);
1112 if (GNUNET_OK != ret)
1113 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
1114 }
1115 /* check reply matches truth */
1116 if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) ||
1117 (0 != memcmp (&gc->challenge_response,
1118 decrypted_truth,
1119 decrypted_truth_size)) )
1120 {
1121 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1122 "Wrong answer provided to secure question had %u bytes, wanted %u\n",
1123 (unsigned int) decrypted_truth_size,
1124 (unsigned int) sizeof (struct GNUNET_HashCode));
1125 return TALER_MHD_reply_with_error (gc->connection,
1126 MHD_HTTP_FORBIDDEN,
1127 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
1128 NULL);
1129 }
1130 /* good, return the key share */
1131 return return_key_share (&gc->truth_uuid,
1132 gc->connection);
1133}
1134
1135
1136/**
1137 * Handle special case of an answer being directly checked by the
1138 * plugin and not by our database. Rate limits answers against brute
1139 * forcing.
1140 *
1141 * @param[in,out] gc request to handle
1142 * @param decrypted_truth hash to check against
1143 * @param decrypted_truth_size number of bytes in @a decrypted_truth
1144 * @return MHD status code
1145 */
1146static MHD_RESULT
1147direct_validation (struct GetContext *gc,
1148 const void *decrypted_truth,
1149 size_t decrypted_truth_size)
1150{
1151 /* Non-random code, call plugin directly! */
1152 enum ANASTASIS_AUTHORIZATION_Result aar;
1153 enum GNUNET_GenericReturnValue res;
1154
1155 res = rate_limit (gc);
1156 if (GNUNET_OK != res)
1157 return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
1158 gc->as = gc->authorization->start (gc->authorization->cls,
1159 &AH_trigger_daemon,
1160 NULL,
1161 &gc->truth_uuid,
1162 0LLU,
1163 decrypted_truth,
1164 decrypted_truth_size);
1165 if (NULL == gc->as)
1166 {
1167 GNUNET_break (0);
1168 return TALER_MHD_reply_with_error (gc->connection,
1169 MHD_HTTP_INTERNAL_SERVER_ERROR,
1170 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
1171 NULL);
1172 }
1173 aar = gc->authorization->process (gc->as,
1174 GNUNET_TIME_UNIT_ZERO_ABS,
1175 gc->connection);
1176 switch (aar)
1177 {
1178 case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
1179 GNUNET_break (0);
1180 return MHD_YES;
1181 case ANASTASIS_AUTHORIZATION_RES_FAILED:
1182 return MHD_YES;
1183 case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
1184 gc_suspended (gc);
1185 return MHD_YES;
1186 case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
1187 GNUNET_break (0);
1188 return MHD_NO;
1189 case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
1190 return MHD_NO;
1191 case ANASTASIS_AUTHORIZATION_RES_FINISHED:
1192 return return_key_share (&gc->truth_uuid,
1193 gc->connection);
1194 }
1195 GNUNET_break (0);
1196 return MHD_NO;
1197}
1198
1199
981MHD_RESULT 1200MHD_RESULT
982AH_handler_truth_get ( 1201AH_handler_truth_get (
983 struct MHD_Connection *connection, 1202 struct MHD_Connection *connection,
@@ -1113,7 +1332,6 @@ AH_handler_truth_get (
1113 GNUNET_TIME_UNIT_SECONDS); 1332 GNUNET_TIME_UNIT_SECONDS);
1114 } 1333 }
1115 } 1334 }
1116
1117 } /* end of first-time initialization (if NULL == gc) */ 1335 } /* end of first-time initialization (if NULL == gc) */
1118 else 1336 else
1119 { 1337 {
@@ -1291,104 +1509,14 @@ AH_handler_truth_get (
1291 but check that the hash matches */ 1509 but check that the hash matches */
1292 if (is_question) 1510 if (is_question)
1293 { 1511 {
1294 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1512 MHD_RESULT ret;
1295 "Handling security question challenge\n");
1296 if (! gc->have_response)
1297 {
1298 GNUNET_free (decrypted_truth);
1299 GNUNET_free (truth_mime);
1300 return TALER_MHD_reply_with_error (connection,
1301 MHD_HTTP_FORBIDDEN,
1302 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
1303 NULL);
1304 }
1305 1513
1306 { 1514 ret = handle_security_question (gc,
1307 enum GNUNET_DB_QueryStatus qs; 1515 decrypted_truth,
1308 struct GNUNET_TIME_Absolute rt; 1516 decrypted_truth_size);
1309 uint64_t code;
1310 enum ANASTASIS_DB_CodeStatus cs;
1311 struct GNUNET_HashCode hc;
1312 bool satisfied;
1313 uint64_t dummy;
1314
1315 rt = GNUNET_TIME_UNIT_FOREVER_ABS;
1316 qs = db->create_challenge_code (db->cls,
1317 &gc->truth_uuid,
1318 MAX_QUESTION_FREQ,
1319 GNUNET_TIME_UNIT_HOURS,
1320 INITIAL_RETRY_COUNTER,
1321 &rt,
1322 &code);
1323 if (0 > qs)
1324 {
1325 GNUNET_break (0 < qs);
1326 GNUNET_free (decrypted_truth);
1327 GNUNET_free (truth_mime);
1328 return TALER_MHD_reply_with_error (connection,
1329 MHD_HTTP_INTERNAL_SERVER_ERROR,
1330 TALER_EC_GENERIC_DB_FETCH_FAILED,
1331 "create_challenge_code (for rate limiting)");
1332 }
1333 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1334 {
1335 GNUNET_free (decrypted_truth);
1336 GNUNET_free (truth_mime);
1337 return TALER_MHD_reply_with_error (connection,
1338 MHD_HTTP_TOO_MANY_REQUESTS,
1339 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1340 NULL);
1341 }
1342 /* decrement trial counter */
1343 ANASTASIS_hash_answer (code + 1, /* always use wrong answer */
1344 &hc);
1345 cs = db->verify_challenge_code (db->cls,
1346 &gc->truth_uuid,
1347 &hc,
1348 &dummy,
1349 &satisfied);
1350 switch (cs)
1351 {
1352 case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
1353 /* good, what we wanted */
1354 break;
1355 case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
1356 case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
1357 GNUNET_break (0);
1358 return TALER_MHD_reply_with_error (gc->connection,
1359 MHD_HTTP_INTERNAL_SERVER_ERROR,
1360 TALER_EC_GENERIC_DB_FETCH_FAILED,
1361 "verify_challenge_code");
1362 case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
1363 return TALER_MHD_reply_with_error (connection,
1364 MHD_HTTP_TOO_MANY_REQUESTS,
1365 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1366 NULL);
1367 case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
1368 /* this should be impossible, we used code+1 */
1369 GNUNET_assert (0);
1370 }
1371 }
1372 if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) ||
1373 (0 != memcmp (&gc->challenge_response,
1374 decrypted_truth,
1375 decrypted_truth_size)) )
1376 {
1377 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1378 "Wrong answer provided to secure question had %u bytes, wanted %u\n",
1379 (unsigned int) decrypted_truth_size,
1380 (unsigned int) sizeof (struct GNUNET_HashCode));
1381 GNUNET_free (decrypted_truth);
1382 GNUNET_free (truth_mime);
1383 return TALER_MHD_reply_with_error (connection,
1384 MHD_HTTP_FORBIDDEN,
1385 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
1386 NULL);
1387 }
1388 GNUNET_free (decrypted_truth);
1389 GNUNET_free (truth_mime); 1517 GNUNET_free (truth_mime);
1390 return return_key_share (&gc->truth_uuid, 1518 GNUNET_free (decrypted_truth);
1391 connection); 1519 return ret;
1392 } 1520 }
1393 1521
1394 /* Not security question, check for answer in DB */ 1522 /* Not security question, check for answer in DB */
@@ -1399,6 +1527,18 @@ AH_handler_truth_get (
1399 uint64_t code; 1527 uint64_t code;
1400 1528
1401 GNUNET_free (truth_mime); 1529 GNUNET_free (truth_mime);
1530 if (gc->authorization->user_provided_code)
1531 {
1532 MHD_RESULT res;
1533
1534 res = direct_validation (gc,
1535 decrypted_truth,
1536 decrypted_truth_size);
1537 GNUNET_free (decrypted_truth);
1538 return res;
1539 }
1540
1541 /* random code, check against database */
1402 cs = db->verify_challenge_code (db->cls, 1542 cs = db->verify_challenge_code (db->cls,
1403 &gc->truth_uuid, 1543 &gc->truth_uuid,
1404 &gc->challenge_response, 1544 &gc->challenge_response,
diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h
index 7bf91a2..cf5a69a 100644
--- a/src/include/anastasis_database_plugin.h
+++ b/src/include/anastasis_database_plugin.h
@@ -633,7 +633,7 @@ struct ANASTASIS_DatabasePlugin
633 (*mark_challenge_code_satisfied)( 633 (*mark_challenge_code_satisfied)(
634 void *cls, 634 void *cls,
635 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 635 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
636 const uint64_t code); 636 uint64_t code);
637 637
638 638
639 /** 639 /**
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
index 897a6dd..8709cf9 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -1684,10 +1684,13 @@ select_challenge_cb (void *cls,
1684 json_object_set_new (sctx->state, 1684 json_object_set_new (sctx->state,
1685 "selected_challenge_uuid", 1685 "selected_challenge_uuid",
1686 GNUNET_JSON_from_data_auto (&cd->uuid))); 1686 GNUNET_JSON_from_data_auto (&cd->uuid)));
1687 if (0 == strcmp ("question", 1687 if ( (0 == strcmp ("question",
1688 cd->type)) 1688 cd->type)) ||
1689 (0 == strcmp ("totp",
1690 cd->type)) )
1689 { 1691 {
1690 /* security question, immediately request user to answer it */ 1692 /* security question or TOTP:
1693 immediately request user to answer it */
1691 set_state (sctx->state, 1694 set_state (sctx->state,
1692 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); 1695 ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
1693 sctx->cb (sctx->cb_cls, 1696 sctx->cb (sctx->cb_cls,