anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis_api_recovery_redux.c (82290B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file reducer/anastasis_api_recovery_redux.c
     18  * @brief anastasis reducer recovery api
     19  * @author Christian Grothoff
     20  * @author Dominik Meister
     21  * @author Dennis Neufeld
     22  */
     23 #include <platform.h>
     24 #include <jansson.h>
     25 #include "anastasis_redux.h"
     26 #include "anastasis_error_codes.h"
     27 #include "anastasis_api_redux.h"
     28 
     29 
     30 #define GENERATE_STRING(STRING) #STRING,
     31 static const char *recovery_strings[] = {
     32   ANASTASIS_RECOVERY_STATES (GENERATE_STRING)
     33 };
     34 #undef GENERATE_STRING
     35 
     36 
     37 enum ANASTASIS_RecoveryState
     38 ANASTASIS_recovery_state_from_string_ (const char *state_string)
     39 {
     40   for (enum ANASTASIS_RecoveryState i = 0;
     41        i < sizeof (recovery_strings) / sizeof(*recovery_strings);
     42        i++)
     43     if (0 == strcmp (state_string,
     44                      recovery_strings[i]))
     45       return i;
     46   return ANASTASIS_RECOVERY_STATE_INVALID;
     47 }
     48 
     49 
     50 const char *
     51 ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs)
     52 {
     53   if ( (rs < 0) ||
     54        (rs >= sizeof (recovery_strings) / sizeof(*recovery_strings)) )
     55   {
     56     GNUNET_break_op (0);
     57     return NULL;
     58   }
     59   return recovery_strings[rs];
     60 }
     61 
     62 
     63 static void
     64 set_state (json_t *state,
     65            enum ANASTASIS_RecoveryState new_recovery_state)
     66 {
     67   GNUNET_assert (
     68     0 ==
     69     json_object_set_new (
     70       state,
     71       "recovery_state",
     72       json_string (ANASTASIS_recovery_state_to_string_ (new_recovery_state))));
     73 }
     74 
     75 
     76 /**
     77  * Returns an initial ANASTASIS recovery state.
     78  *
     79  * @return NULL on failure
     80  */
     81 json_t *
     82 ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
     83 {
     84   json_t *initial_state;
     85   const char *external_reducer = ANASTASIS_REDUX_probe_external_reducer ();
     86 
     87   if (NULL != external_reducer)
     88   {
     89     int pipefd_stdout[2];
     90     pid_t pid = 0;
     91     int status;
     92     FILE *reducer_stdout;
     93 
     94     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     95                 "Using external reducer '%s' for recovery start status\n",
     96                 external_reducer);
     97 
     98     GNUNET_assert (0 == pipe (pipefd_stdout));
     99     pid = fork ();
    100     if (pid == 0)
    101     {
    102       GNUNET_assert (0 ==
    103                      close (pipefd_stdout[0]));
    104       GNUNET_assert (STDOUT_FILENO ==
    105                      dup2 (pipefd_stdout[1],
    106                            STDOUT_FILENO));
    107       execlp (external_reducer,
    108               external_reducer,
    109               "-r",
    110               NULL);
    111       GNUNET_assert (0);
    112     }
    113 
    114     GNUNET_assert (0 ==
    115                    close (pipefd_stdout[1]));
    116     reducer_stdout = fdopen (pipefd_stdout[0],
    117                              "r");
    118     {
    119       json_error_t err;
    120 
    121       initial_state = json_loadf (reducer_stdout,
    122                                   0,
    123                                   &err);
    124 
    125       if (NULL == initial_state)
    126       {
    127         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    128                     "External reducer did not output valid JSON: %s:%d:%d %s\n",
    129                     err.source,
    130                     err.line,
    131                     err.column,
    132                     err.text);
    133         GNUNET_assert (0 == fclose (reducer_stdout));
    134         waitpid (pid, &status, 0);
    135         return NULL;
    136       }
    137     }
    138 
    139     GNUNET_assert (NULL != initial_state);
    140     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    141                 "Waiting for external reducer to terminate.\n");
    142     GNUNET_assert (0 == fclose (reducer_stdout));
    143     reducer_stdout = NULL;
    144     waitpid (pid, &status, 0);
    145 
    146     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    147                 "External reducer finished with exit status '%d'\n",
    148                 status);
    149     return initial_state;
    150   }
    151 
    152   (void) cfg;
    153   initial_state = ANASTASIS_REDUX_load_continents_ ();
    154   if (NULL == initial_state)
    155     return NULL;
    156   GNUNET_assert (
    157     0 ==
    158     json_object_set_new (initial_state,
    159                          "reducer_type",
    160                          json_string ("recovery")));
    161   set_state (initial_state,
    162              ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING);
    163   return initial_state;
    164 }
    165 
    166 
    167 /**
    168  * Context for a "select_challenge" operation.
    169  */
    170 struct SelectChallengeContext
    171 {
    172   /**
    173    * Handle we returned for cancellation of the operation.
    174    */
    175   struct ANASTASIS_ReduxAction ra;
    176 
    177   /**
    178    * UUID of the challenge selected by the user for solving.
    179    */
    180   struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
    181 
    182   /**
    183    * Which timeout was set for the operation?
    184    */
    185   struct GNUNET_TIME_Relative timeout;
    186 
    187   /**
    188    * Overall recovery action.
    189    */
    190   struct ANASTASIS_Recovery *r;
    191 
    192   /**
    193    * Function to call with the next state.
    194    */
    195   ANASTASIS_ActionCallback cb;
    196 
    197   /**
    198    * Closure for @e cb.
    199    */
    200   void *cb_cls;
    201 
    202   /**
    203    * Our state.
    204    */
    205   json_t *state;
    206 
    207   /**
    208    * Our arguments (like answers to the challenge, if already provided).
    209    */
    210   json_t *args;
    211 
    212   /**
    213    * Task scheduled for delayed success reporting. Needed to make
    214    * sure that the solved challenge was really the final result,
    215    * cancelled if the solved challenge resulted in the secret being
    216    * recovered.
    217    */
    218   struct GNUNET_SCHEDULER_Task *delayed_report;
    219 
    220   /**
    221    * Payment secret, if we are in the "pay" state.
    222    */
    223   struct ANASTASIS_PaymentSecretP ps;
    224 
    225   /**
    226    * Application asked us to only poll for existing
    227    * asynchronous challenges, and not to being a
    228    * new one.
    229    */
    230   bool poll_only;
    231 };
    232 
    233 
    234 /**
    235  * Cleanup a select challenge context.
    236  *
    237  * @param cls a `struct SelectChallengeContext *`
    238  */
    239 static void
    240 sctx_free (void *cls)
    241 {
    242   struct SelectChallengeContext *sctx = cls;
    243 
    244   if (NULL != sctx->r)
    245   {
    246     ANASTASIS_recovery_abort (sctx->r);
    247     sctx->r = NULL;
    248   }
    249   json_decref (sctx->state);
    250   json_decref (sctx->args);
    251   if (NULL != sctx->delayed_report)
    252   {
    253     GNUNET_SCHEDULER_cancel (sctx->delayed_report);
    254     sctx->delayed_report = NULL;
    255   }
    256   GNUNET_free (sctx);
    257 }
    258 
    259 
    260 /**
    261  * Call the action callback with an error result
    262  *
    263  * @param cb action callback to call
    264  * @param cb_cls closure for @a cb
    265  * @param rc error code to translate to JSON
    266  */
    267 static void
    268 fail_by_error (ANASTASIS_ActionCallback cb,
    269                void *cb_cls,
    270                enum ANASTASIS_RecoveryStatus rc)
    271 {
    272   const char *msg = NULL;
    273   enum TALER_ErrorCode ec = TALER_EC_INVALID;
    274 
    275   switch (rc)
    276   {
    277   case ANASTASIS_RS_SUCCESS:
    278     GNUNET_assert (0);
    279     break;
    280   case ANASTASIS_RS_POLICY_DOWNLOAD_FAILED:
    281     msg = gettext_noop ("download failed due to unexpected network issue");
    282     ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
    283     break;
    284   case ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY:
    285     GNUNET_break (0);
    286     msg = gettext_noop ("policy document returned was malformed");
    287     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    288     break;
    289   case ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG:
    290     GNUNET_break (0);
    291     msg = gettext_noop ("policy document too large for client memory");
    292     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    293     break;
    294   case ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION:
    295     GNUNET_break (0);
    296     msg = gettext_noop ("failed to decompress policy document");
    297     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    298     break;
    299   case ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON:
    300     GNUNET_break (0);
    301     msg = gettext_noop ("policy document returned was not in JSON format");
    302     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    303     break;
    304   case ANASTASIS_RS_POLICY_MALFORMED_JSON:
    305     GNUNET_break (0);
    306     msg = gettext_noop (
    307       "policy document returned was not in required JSON format");
    308     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
    309     break;
    310   case ANASTASIS_RS_POLICY_SERVER_ERROR:
    311     msg = gettext_noop ("Anastasis server reported transient internal error");
    312     ec = TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED;
    313     break;
    314   case ANASTASIS_RS_POLICY_GONE:
    315     msg = gettext_noop ("policy document no longer exists");
    316     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
    317     break;
    318   case ANASTASIS_RS_POLICY_UNKNOWN:
    319     msg = gettext_noop ("account unknown to Anastasis server");
    320     ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
    321     break;
    322   }
    323   ANASTASIS_redux_fail_ (cb,
    324                          cb_cls,
    325                          ec,
    326                          msg);
    327 }
    328 
    329 
    330 /**
    331  * This function is called whenever the recovery process ends.
    332  * On success, the secret is returned in @a secret.
    333  *
    334  * @param cls handle for the callback
    335  * @param rc error code
    336  * @param secret contains the core secret which is passed to the user
    337  * @param secret_size defines the size of the core secret
    338  */
    339 static void
    340 core_secret_cb (void *cls,
    341                 enum ANASTASIS_RecoveryStatus rc,
    342                 const void *secret,
    343                 size_t secret_size)
    344 {
    345   struct SelectChallengeContext *sctx = cls;
    346 
    347   sctx->r = NULL;
    348   if (ANASTASIS_RS_SUCCESS == rc)
    349   {
    350     json_t *jsecret;
    351 
    352     jsecret = json_loadb (secret,
    353                           secret_size,
    354                           JSON_REJECT_DUPLICATES,
    355                           NULL);
    356     if (NULL == jsecret)
    357     {
    358       ANASTASIS_redux_fail_ (sctx->cb,
    359                              sctx->cb_cls,
    360                              TALER_EC_ANASTASIS_REDUCER_SECRET_MALFORMED,
    361                              NULL);
    362       sctx_free (sctx);
    363       return;
    364     }
    365     GNUNET_assert (0 ==
    366                    json_object_set_new (sctx->state,
    367                                         "core_secret",
    368                                         jsecret));
    369     set_state (sctx->state,
    370                ANASTASIS_RECOVERY_STATE_RECOVERY_FINISHED);
    371     sctx->cb (sctx->cb_cls,
    372               TALER_EC_NONE,
    373               sctx->state);
    374     sctx_free (sctx);
    375     return;
    376   }
    377   fail_by_error (sctx->cb,
    378                  sctx->cb_cls,
    379                  rc);
    380   sctx_free (sctx);
    381 }
    382 
    383 
    384 /**
    385  * A challenge was solved, but we are not yet finished.
    386  * Report to caller that the challenge was completed.
    387  *
    388  * @param cls a `struct SelectChallengeContext`
    389  */
    390 static void
    391 report_solved (void *cls)
    392 {
    393   struct SelectChallengeContext *sctx = cls;
    394 
    395   sctx->delayed_report = NULL;
    396   set_state (sctx->state,
    397              ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    398   sctx->cb (sctx->cb_cls,
    399             TALER_EC_NONE,
    400             sctx->state);
    401   sctx_free (sctx);
    402 }
    403 
    404 
    405 /**
    406  * Find challenge of @a uuid in @a state under "recovery_information".
    407  *
    408  * @param state the state to search
    409  * @param uuid the UUID to search for
    410  * @return NULL on error, otherwise challenge entry; RC is NOT incremented
    411  */
    412 static json_t *
    413 find_challenge_in_ri (json_t *state,
    414                       const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
    415 {
    416   struct ANASTASIS_CRYPTO_TruthUUIDP u;
    417   struct GNUNET_JSON_Specification spec[] = {
    418     GNUNET_JSON_spec_fixed_auto ("uuid",
    419                                  &u),
    420     GNUNET_JSON_spec_end ()
    421   };
    422   json_t *ri;
    423   json_t *challenges;
    424   json_t *challenge;
    425   size_t index;
    426 
    427   ri = json_object_get (state,
    428                         "recovery_information");
    429   if (NULL == ri)
    430   {
    431     GNUNET_break (0);
    432     return NULL;
    433   }
    434   challenges = json_object_get (ri,
    435                                 "challenges");
    436   if (NULL == challenges)
    437   {
    438     GNUNET_break (0);
    439     return NULL;
    440   }
    441   json_array_foreach (challenges, index, challenge)
    442   {
    443     if (GNUNET_OK !=
    444         GNUNET_JSON_parse (challenge,
    445                            spec,
    446                            NULL, NULL))
    447     {
    448       GNUNET_break (0);
    449       return NULL;
    450     }
    451     if (0 ==
    452         GNUNET_memcmp (&u,
    453                        uuid))
    454     {
    455       return challenge;
    456     }
    457   }
    458   return NULL;
    459 }
    460 
    461 
    462 /**
    463  * Find challenge of @a uuid in @a state under "challenges".
    464  *
    465  * @param state the state to search
    466  * @param uuid the UUID to search for
    467  * @return NULL on error, otherwise challenge entry; RC is NOT incremented
    468  */
    469 static json_t *
    470 find_challenge_in_cs (json_t *state,
    471                       const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
    472 {
    473   json_t *rd = json_object_get (state,
    474                                 "recovery_document");
    475   json_t *cs = json_object_get (rd,
    476                                 "challenges");
    477   json_t *c;
    478   size_t off;
    479 
    480   json_array_foreach (cs, off, c)
    481   {
    482     struct ANASTASIS_CRYPTO_TruthUUIDP u;
    483     struct GNUNET_JSON_Specification spec[] = {
    484       GNUNET_JSON_spec_fixed_auto ("uuid",
    485                                    &u),
    486       GNUNET_JSON_spec_end ()
    487     };
    488 
    489     if (GNUNET_OK !=
    490         GNUNET_JSON_parse (c,
    491                            spec,
    492                            NULL, NULL))
    493     {
    494       GNUNET_break (0);
    495       continue;
    496     }
    497     if (0 !=
    498         GNUNET_memcmp (uuid,
    499                        &u))
    500       continue;
    501     return c;
    502   }
    503   return NULL;
    504 }
    505 
    506 
    507 /**
    508  * Defines a callback for the response status for a challenge start
    509  * operation.
    510  *
    511  * @param cls a `struct SelectChallengeContext *`
    512  * @param csr response details
    513  */
    514 static void
    515 start_feedback_cb (
    516   void *cls,
    517   const struct ANASTASIS_ChallengeStartResponse *csr)
    518 {
    519   struct SelectChallengeContext *sctx = cls;
    520   const struct ANASTASIS_ChallengeDetails *cd;
    521   char uuid[sizeof (cd->uuid) * 2];
    522   char *end;
    523   json_t *feedback;
    524 
    525   cd = ANASTASIS_challenge_get_details (csr->challenge);
    526   end = GNUNET_STRINGS_data_to_string (&cd->uuid,
    527                                        sizeof (cd->uuid),
    528                                        uuid,
    529                                        sizeof (uuid));
    530   GNUNET_assert (NULL != end);
    531   *end = '\0';
    532   feedback = json_object_get (sctx->state,
    533                               "challenge_feedback");
    534   if (NULL == feedback)
    535   {
    536     feedback = json_object ();
    537     GNUNET_assert (0 ==
    538                    json_object_set_new (sctx->state,
    539                                         "challenge_feedback",
    540                                         feedback));
    541   }
    542   switch (csr->cs)
    543   {
    544   case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED:
    545     {
    546       json_t *instructions;
    547       char *hint;
    548 
    549       GNUNET_asprintf (&hint,
    550                        _ ("Required TAN can be found in `%s'"),
    551                        csr->details.tan_filename);
    552       instructions = GNUNET_JSON_PACK (
    553         GNUNET_JSON_pack_string ("state",
    554                                  "code-in-file"),
    555         GNUNET_JSON_pack_string ("filename",
    556                                  csr->details.tan_filename),
    557         GNUNET_JSON_pack_string ("display_hint",
    558                                  hint));
    559       GNUNET_free (hint);
    560       GNUNET_assert (0 ==
    561                      json_object_set_new (feedback,
    562                                           uuid,
    563                                           instructions));
    564     }
    565     set_state (sctx->state,
    566                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    567     sctx->cb (sctx->cb_cls,
    568               TALER_EC_NONE,
    569               sctx->state);
    570     sctx_free (sctx);
    571     return;
    572   case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED:
    573     {
    574       json_t *instructions;
    575       char *hint;
    576 
    577       GNUNET_asprintf (&hint,
    578                        _ ("TAN code was sent to `%s'"),
    579                        csr->details.tan_address_hint);
    580       instructions = GNUNET_JSON_PACK (
    581         GNUNET_JSON_pack_string ("state",
    582                                  "send-to-address"),
    583         GNUNET_JSON_pack_string ("address_hint",
    584                                  csr->details.tan_address_hint),
    585         GNUNET_JSON_pack_string ("display_hint",
    586                                  hint));
    587       GNUNET_free (hint);
    588       GNUNET_assert (0 ==
    589                      json_object_set_new (feedback,
    590                                           uuid,
    591                                           instructions));
    592     }
    593     set_state (sctx->state,
    594                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    595     sctx->cb (sctx->cb_cls,
    596               TALER_EC_NONE,
    597               sctx->state);
    598     sctx_free (sctx);
    599     return;
    600 
    601   case ANASTASIS_CHALLENGE_START_STATUS_TAN_ALREADY_SENT:
    602     {
    603       json_t *instructions;
    604       char *hint;
    605 
    606       GNUNET_asprintf (&hint,
    607                        _ ("TAN code already sent."));
    608       instructions = GNUNET_JSON_PACK (
    609         GNUNET_JSON_pack_string ("state",
    610                                  "send-to-address"),
    611         GNUNET_JSON_pack_string ("display_hint",
    612                                  hint));
    613       GNUNET_free (hint);
    614       GNUNET_assert (0 ==
    615                      json_object_set_new (feedback,
    616                                           uuid,
    617                                           instructions));
    618     }
    619     set_state (sctx->state,
    620                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    621     sctx->cb (sctx->cb_cls,
    622               TALER_EC_NONE,
    623               sctx->state);
    624     sctx_free (sctx);
    625     return;
    626 
    627   case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED:
    628     {
    629       json_t *pay;
    630       char *hint;
    631 
    632       GNUNET_asprintf (&hint,
    633                        _ ("Taler payment to `%s' required"),
    634                        csr->details.payment_required.taler_pay_uri);
    635       pay = GNUNET_JSON_PACK (
    636         GNUNET_JSON_pack_string ("state",
    637                                  "taler-payment"),
    638         GNUNET_JSON_pack_string (
    639           "taler_pay_uri",
    640           csr->details.payment_required.taler_pay_uri),
    641         GNUNET_JSON_pack_string ("provider",
    642                                  cd->provider_url),
    643         GNUNET_JSON_pack_string ("display_hint",
    644                                  hint),
    645         GNUNET_JSON_pack_data_auto (
    646           "payment_secret",
    647           &csr->details.payment_required.payment_secret));
    648       GNUNET_free (hint);
    649       GNUNET_assert (0 ==
    650                      json_object_set_new (feedback,
    651                                           uuid,
    652                                           pay));
    653     }
    654     /* Remember payment secret for later (once application claims it paid) */
    655     {
    656       json_t *challenge = find_challenge_in_ri (sctx->state,
    657                                                 &cd->uuid);
    658 
    659       GNUNET_assert (NULL != challenge);
    660       GNUNET_assert (0 ==
    661                      json_object_set_new (
    662                        challenge,
    663                        "payment_secret",
    664                        GNUNET_JSON_from_data_auto (
    665                          &csr->details.payment_required.payment_secret)));
    666     }
    667     set_state (sctx->state,
    668                ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
    669     sctx->cb (sctx->cb_cls,
    670               TALER_EC_NONE,
    671               sctx->state);
    672     sctx_free (sctx);
    673     return;
    674   case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE:
    675     {
    676       json_t *err;
    677 
    678       err = GNUNET_JSON_PACK (
    679         GNUNET_JSON_pack_string ("state",
    680                                  "server-failure"),
    681         GNUNET_JSON_pack_uint64 ("http_status",
    682                                  csr->http_status),
    683         GNUNET_JSON_pack_uint64 ("error_code",
    684                                  csr->ec));
    685       GNUNET_assert (0 ==
    686                      json_object_set_new (feedback,
    687                                           uuid,
    688                                           err));
    689     }
    690     set_state (sctx->state,
    691                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    692     sctx->cb (sctx->cb_cls,
    693               csr->ec,
    694               sctx->state);
    695     sctx_free (sctx);
    696     return;
    697   case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN:
    698     {
    699       json_t *err;
    700 
    701       err = GNUNET_JSON_PACK (
    702         GNUNET_JSON_pack_string ("state",
    703                                  "truth-unknown"),
    704         GNUNET_JSON_pack_uint64 ("http_status",
    705                                  csr->http_status),
    706         GNUNET_JSON_pack_uint64 ("error_code",
    707                                  TALER_EC_ANASTASIS_TRUTH_UNKNOWN));
    708       GNUNET_assert (0 ==
    709                      json_object_set_new (feedback,
    710                                           uuid,
    711                                           err));
    712     }
    713     set_state (sctx->state,
    714                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    715     sctx->cb (sctx->cb_cls,
    716               TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
    717               sctx->state);
    718     sctx_free (sctx);
    719     return;
    720   case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED:
    721     {
    722       json_t *reply;
    723       json_t *c;
    724       char *hint;
    725 
    726       c = find_challenge_in_cs (sctx->state,
    727                                 &cd->uuid);
    728       if (NULL == c)
    729       {
    730         GNUNET_break (0);
    731         ANASTASIS_redux_fail_ (sctx->cb,
    732                                sctx->cb_cls,
    733                                TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    734                                NULL);
    735         sctx_free (sctx);
    736         return;
    737       }
    738       GNUNET_assert (0 ==
    739                      json_object_set_new (c,
    740                                           "async",
    741                                           json_true ()));
    742       GNUNET_assert (
    743         0 ==
    744         json_object_set_new (
    745           c,
    746           "answer-pin",
    747           json_integer (
    748             csr->details.bank_transfer_required.answer_code)));
    749       GNUNET_asprintf (&hint,
    750                        _ ("Wire %s to %s (%s) with subject %s\n"),
    751                        TALER_amount2s (
    752                          &csr->details.bank_transfer_required.amount),
    753                        csr->details.bank_transfer_required.target_iban,
    754                        csr->details.bank_transfer_required.target_business_name,
    755                        csr->details.bank_transfer_required.wire_transfer_subject
    756                        );
    757       reply = GNUNET_JSON_PACK (
    758         GNUNET_JSON_pack_string ("state",
    759                                  "iban-instructions"),
    760         GNUNET_JSON_pack_string (
    761           "target_iban",
    762           csr->details.bank_transfer_required.target_iban),
    763         GNUNET_JSON_pack_string (
    764           "display_hint",
    765           hint),
    766         GNUNET_JSON_pack_string (
    767           "target_business_name",
    768           csr->details.bank_transfer_required.target_business_name),
    769         GNUNET_JSON_pack_string (
    770           "wire_transfer_subject",
    771           csr->details.bank_transfer_required.wire_transfer_subject),
    772         TALER_JSON_pack_amount (
    773           "challenge_amount",
    774           &csr->details.bank_transfer_required.amount));
    775       GNUNET_free (hint);
    776       GNUNET_assert (0 ==
    777                      json_object_set_new (feedback,
    778                                           uuid,
    779                                           reply));
    780     }
    781     GNUNET_assert (0 ==
    782                    json_object_set_new (sctx->state,
    783                                         "selected_challenge_uuid",
    784                                         GNUNET_JSON_from_data_auto (
    785                                           &cd->uuid)));
    786     set_state (sctx->state,
    787                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    788     sctx->cb (sctx->cb_cls,
    789               TALER_EC_NONE,
    790               sctx->state);
    791     sctx_free (sctx);
    792     return;
    793   }
    794   GNUNET_break (0);
    795   ANASTASIS_redux_fail_ (sctx->cb,
    796                          sctx->cb_cls,
    797                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    798                          NULL);
    799   sctx_free (sctx);
    800 }
    801 
    802 
    803 /**
    804  * Defines a callback for the response status for a challenge answer
    805  * operation.
    806  *
    807  * @param cls a `struct SelectChallengeContext *`
    808  * @param csr response details
    809  */
    810 static void
    811 answer_feedback_cb (
    812   void *cls,
    813   const struct ANASTASIS_ChallengeAnswerResponse *csr)
    814 {
    815   struct SelectChallengeContext *sctx = cls;
    816   const struct ANASTASIS_ChallengeDetails *cd;
    817   char uuid[sizeof (cd->uuid) * 2];
    818   char *end;
    819   json_t *feedback;
    820 
    821   cd = ANASTASIS_challenge_get_details (csr->challenge);
    822   end = GNUNET_STRINGS_data_to_string (&cd->uuid,
    823                                        sizeof (cd->uuid),
    824                                        uuid,
    825                                        sizeof (uuid));
    826   GNUNET_assert (NULL != end);
    827   *end = '\0';
    828   feedback = json_object_get (sctx->state,
    829                               "challenge_feedback");
    830   if (NULL == feedback)
    831   {
    832     feedback = json_object ();
    833     GNUNET_assert (0 ==
    834                    json_object_set_new (sctx->state,
    835                                         "challenge_feedback",
    836                                         feedback));
    837   }
    838   switch (csr->cs)
    839   {
    840   case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED:
    841     {
    842       json_t *rd;
    843 
    844       rd = ANASTASIS_recovery_serialize (sctx->r);
    845       if (NULL == rd)
    846       {
    847         GNUNET_break (0);
    848         ANASTASIS_redux_fail_ (sctx->cb,
    849                                sctx->cb_cls,
    850                                TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    851                                "unable to serialize recovery state");
    852         sctx_free (sctx);
    853         return;
    854       }
    855       GNUNET_assert (0 ==
    856                      json_object_set_new (sctx->state,
    857                                           "recovery_document",
    858                                           rd));
    859     }
    860     {
    861       json_t *solved;
    862 
    863       solved = GNUNET_JSON_PACK (
    864         GNUNET_JSON_pack_string ("state",
    865                                  "solved"));
    866       GNUNET_assert (0 ==
    867                      json_object_set_new (feedback,
    868                                           uuid,
    869                                           solved));
    870     }
    871     /* Delay reporting challenge success, as we MAY still
    872        also see a secret recovery success (and we can only
    873        call the callback once) */
    874     sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved,
    875                                                      sctx);
    876     return;
    877   case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER:
    878     {
    879       json_t *instructions;
    880 
    881       instructions = GNUNET_JSON_PACK (
    882         GNUNET_JSON_pack_string ("state",
    883                                  "incorrect-answer"),
    884         GNUNET_JSON_pack_uint64 ("error_code",
    885                                  csr->ec));
    886       GNUNET_assert (0 ==
    887                      json_object_set_new (feedback,
    888                                           uuid,
    889                                           instructions));
    890     }
    891     set_state (sctx->state,
    892                ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
    893     sctx->cb (sctx->cb_cls,
    894               TALER_EC_NONE,
    895               sctx->state);
    896     sctx_free (sctx);
    897     return;
    898   case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED:
    899     {
    900       json_t *pay;
    901       char *hint;
    902 
    903       GNUNET_asprintf (&hint,
    904                        _ ("Taler payment to `%s' required"),
    905                        csr->details.payment_required.taler_pay_uri);
    906       pay = GNUNET_JSON_PACK (
    907         GNUNET_JSON_pack_string ("state",
    908                                  "taler-payment"),
    909         GNUNET_JSON_pack_string (
    910           "taler_pay_uri",
    911           csr->details.payment_required.taler_pay_uri),
    912         GNUNET_JSON_pack_string (
    913           "display_hint",
    914           hint),
    915         GNUNET_JSON_pack_string ("provider",
    916                                  cd->provider_url),
    917         GNUNET_JSON_pack_data_auto (
    918           "payment_secret",
    919           &csr->details.payment_required.payment_secret));
    920       GNUNET_free (hint);
    921       GNUNET_assert (0 ==
    922                      json_object_set_new (feedback,
    923                                           uuid,
    924                                           pay));
    925     }
    926     /* Remember payment secret for later (once application claims it paid) */
    927     {
    928       json_t *challenge = find_challenge_in_ri (sctx->state,
    929                                                 &cd->uuid);
    930 
    931       GNUNET_assert (NULL != challenge);
    932       GNUNET_assert (0 ==
    933                      json_object_set_new (
    934                        challenge,
    935                        "payment_secret",
    936                        GNUNET_JSON_from_data_auto (
    937                          &csr->details.payment_required.payment_secret)));
    938     }
    939     set_state (sctx->state,
    940                ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
    941     sctx->cb (sctx->cb_cls,
    942               TALER_EC_NONE,
    943               sctx->state);
    944     sctx_free (sctx);
    945     return;
    946   case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE:
    947     {
    948       json_t *err;
    949 
    950       err = GNUNET_JSON_PACK (
    951         GNUNET_JSON_pack_string ("state",
    952                                  "server-failure"),
    953         GNUNET_JSON_pack_uint64 ("http_status",
    954                                  csr->http_status),
    955         GNUNET_JSON_pack_uint64 ("error_code",
    956                                  csr->ec));
    957       GNUNET_assert (0 ==
    958                      json_object_set_new (feedback,
    959                                           uuid,
    960                                           err));
    961     }
    962     set_state (sctx->state,
    963                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    964     sctx->cb (sctx->cb_cls,
    965               csr->ec,
    966               sctx->state);
    967     sctx_free (sctx);
    968     return;
    969   case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN:
    970     {
    971       json_t *err;
    972 
    973       err = GNUNET_JSON_PACK (
    974         GNUNET_JSON_pack_string ("state",
    975                                  "truth-unknown"),
    976         GNUNET_JSON_pack_uint64 ("http_status",
    977                                  csr->http_status),
    978         GNUNET_JSON_pack_uint64 ("error_code",
    979                                  TALER_EC_ANASTASIS_TRUTH_UNKNOWN));
    980       GNUNET_assert (0 ==
    981                      json_object_set_new (feedback,
    982                                           uuid,
    983                                           err));
    984     }
    985     set_state (sctx->state,
    986                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
    987     sctx->cb (sctx->cb_cls,
    988               TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
    989               sctx->state);
    990     sctx_free (sctx);
    991     return;
    992   case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED:
    993     {
    994       json_t *err;
    995       char *hint;
    996 
    997       GNUNET_asprintf (
    998         &hint,
    999         _ ("exceeded limit of %llu attempts in %s"),
   1000         (unsigned long long) csr->details.rate_limit_exceeded.request_limit,
   1001         GNUNET_TIME_relative2s (
   1002           csr->details.rate_limit_exceeded.request_frequency,
   1003           true));
   1004       err = GNUNET_JSON_PACK (
   1005         GNUNET_JSON_pack_string (
   1006           "state",
   1007           "rate-limit-exceeded"),
   1008         GNUNET_JSON_pack_string (
   1009           "display_hint",
   1010           hint),
   1011         GNUNET_JSON_pack_uint64 (
   1012           "request_limit",
   1013           csr->details.rate_limit_exceeded.request_limit),
   1014         GNUNET_JSON_pack_time_rel (
   1015           "request_frequency",
   1016           csr->details.rate_limit_exceeded.request_frequency),
   1017         GNUNET_JSON_pack_uint64 (
   1018           "error_code",
   1019           TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED));
   1020       GNUNET_free (hint);
   1021       GNUNET_assert (0 ==
   1022                      json_object_set_new (feedback,
   1023                                           uuid,
   1024                                           err));
   1025     }
   1026     set_state (sctx->state,
   1027                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
   1028     sctx->cb (sctx->cb_cls,
   1029               TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
   1030               sctx->state);
   1031     sctx_free (sctx);
   1032     return;
   1033   }
   1034   GNUNET_break (0);
   1035   ANASTASIS_redux_fail_ (sctx->cb,
   1036                          sctx->cb_cls,
   1037                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1038                          NULL);
   1039   sctx_free (sctx);
   1040 }
   1041 
   1042 
   1043 /**
   1044  * Callback which passes back the recovery document and its possible
   1045  * policies. Also passes back the version of the document for the user
   1046  * to check.
   1047  *
   1048  * We find the selected challenge and try to answer it (or begin
   1049  * the process).
   1050  *
   1051  * @param cls a `struct SelectChallengeContext *`
   1052  * @param ri recovery information struct which contains the policies
   1053  */
   1054 static void
   1055 solve_challenge_cb (void *cls,
   1056                     const struct ANASTASIS_RecoveryInformation *ri)
   1057 {
   1058   struct SelectChallengeContext *sctx = cls;
   1059   const struct ANASTASIS_PaymentSecretP *psp = NULL;
   1060   struct ANASTASIS_PaymentSecretP ps;
   1061   struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
   1062   struct GNUNET_JSON_Specification tspec[] = {
   1063     GNUNET_JSON_spec_mark_optional (
   1064       GNUNET_JSON_spec_relative_time ("timeout",
   1065                                       &timeout),
   1066       NULL),
   1067     GNUNET_JSON_spec_end ()
   1068   };
   1069   struct GNUNET_JSON_Specification pspec[] = {
   1070     GNUNET_JSON_spec_fixed_auto ("payment_secret",
   1071                                  &ps),
   1072     GNUNET_JSON_spec_end ()
   1073   };
   1074 
   1075   if (NULL == ri)
   1076   {
   1077     GNUNET_break_op (0);
   1078     ANASTASIS_redux_fail_ (sctx->cb,
   1079                            sctx->cb_cls,
   1080                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1081                            "recovery information could not be deserialized");
   1082     sctx_free (sctx);
   1083     return;
   1084   }
   1085 
   1086   if ( (NULL != sctx->args) &&
   1087        (GNUNET_OK !=
   1088         GNUNET_JSON_parse (sctx->args,
   1089                            tspec,
   1090                            NULL, NULL)) )
   1091   {
   1092     GNUNET_break_op (0);
   1093     ANASTASIS_redux_fail_ (sctx->cb,
   1094                            sctx->cb_cls,
   1095                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1096                            "'timeout' malformed");
   1097     sctx_free (sctx);
   1098     return;
   1099   }
   1100 
   1101   /* resume all async, unsolved challenges */
   1102   {
   1103     bool poll_started = false;
   1104 
   1105     for (unsigned int i = 0; i<ri->cs_len; i++)
   1106     {
   1107       struct ANASTASIS_Challenge *ci = ri->cs[i];
   1108       const struct ANASTASIS_ChallengeDetails *cd;
   1109       json_t *challenge;
   1110       json_t *pin;
   1111 
   1112       cd = ANASTASIS_challenge_get_details (ci);
   1113       if (cd->solved ||
   1114           (! cd->async) )
   1115         continue;
   1116 
   1117       challenge = find_challenge_in_cs (sctx->state,
   1118                                         &cd->uuid);
   1119       if (NULL == challenge)
   1120       {
   1121         GNUNET_break_op (0);
   1122         ANASTASIS_redux_fail_ (sctx->cb,
   1123                                sctx->cb_cls,
   1124                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1125                                "challenge not found");
   1126         sctx_free (sctx);
   1127         return;
   1128       }
   1129       pin = json_object_get (challenge,
   1130                              "answer-pin");
   1131       if (! json_is_integer (pin))
   1132       {
   1133         GNUNET_break_op (0);
   1134         ANASTASIS_redux_fail_ (sctx->cb,
   1135                                sctx->cb_cls,
   1136                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1137                                "async challenge 'answer-pin' not found");
   1138         sctx_free (sctx);
   1139         return;
   1140       }
   1141       if (GNUNET_OK !=
   1142           ANASTASIS_challenge_answer2 (ci,
   1143                                        psp,
   1144                                        timeout,
   1145                                        json_integer_value (pin),
   1146                                        &answer_feedback_cb,
   1147                                        sctx))
   1148       {
   1149         ANASTASIS_redux_fail_ (sctx->cb,
   1150                                sctx->cb_cls,
   1151                                TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1152                                "Failed to begin answering asynchronous challenge");
   1153         sctx_free (sctx);
   1154         return;
   1155       }
   1156       poll_started = true;
   1157     }
   1158 
   1159     if (sctx->poll_only)
   1160     {
   1161       if (! poll_started)
   1162       {
   1163         GNUNET_break_op (0);
   1164         ANASTASIS_redux_fail_ (sctx->cb,
   1165                                sctx->cb_cls,
   1166                                TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
   1167                                "no challenge available for polling");
   1168         return;
   1169       }
   1170       /* only polling, do not start new challenges */
   1171       return;
   1172     }
   1173   } /* end resuming async challenges */
   1174 
   1175   /* Check if we got a payment_secret */
   1176   {
   1177     json_t *challenge;
   1178 
   1179     challenge = find_challenge_in_ri (sctx->state,
   1180                                       &sctx->uuid);
   1181     if (NULL == challenge)
   1182     {
   1183       GNUNET_break_op (0);
   1184       ANASTASIS_redux_fail_ (sctx->cb,
   1185                              sctx->cb_cls,
   1186                              TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1187                              "challenge not found");
   1188       sctx_free (sctx);
   1189       return;
   1190     }
   1191 
   1192     if (NULL !=
   1193         json_object_get (sctx->args,
   1194                          "payment_secret"))
   1195     {
   1196       /* check if we got payment secret in args */
   1197       if (GNUNET_OK !=
   1198           GNUNET_JSON_parse (sctx->args,
   1199                              pspec,
   1200                              NULL, NULL))
   1201       {
   1202         GNUNET_break_op (0);
   1203         ANASTASIS_redux_fail_ (sctx->cb,
   1204                                sctx->cb_cls,
   1205                                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1206                                "'payment_secret' malformed");
   1207         sctx_free (sctx);
   1208         return;
   1209       }
   1210       psp = &ps;
   1211     }
   1212     else if (NULL !=
   1213              json_object_get (challenge,
   1214                               "payment_secret"))
   1215     {
   1216       if (GNUNET_OK !=
   1217           GNUNET_JSON_parse (challenge,
   1218                              pspec,
   1219                              NULL, NULL))
   1220       {
   1221         GNUNET_break_op (0);
   1222         ANASTASIS_redux_fail_ (sctx->cb,
   1223                                sctx->cb_cls,
   1224                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1225                                "'payment_secret' malformed");
   1226         sctx_free (sctx);
   1227         return;
   1228       }
   1229       psp = &ps;
   1230     }
   1231   }
   1232 
   1233   /* start or solve selected challenge */
   1234   for (unsigned int i = 0; i<ri->cs_len; i++)
   1235   {
   1236     struct ANASTASIS_Challenge *ci = ri->cs[i];
   1237     const struct ANASTASIS_ChallengeDetails *cd;
   1238     int ret;
   1239     json_t *c;
   1240 
   1241     cd = ANASTASIS_challenge_get_details (ci);
   1242     if (cd->async)
   1243       continue; /* handled above */
   1244     if (0 !=
   1245         GNUNET_memcmp (&sctx->uuid,
   1246                        &cd->uuid))
   1247       continue;
   1248     if (cd->solved)
   1249     {
   1250       ANASTASIS_redux_fail_ (sctx->cb,
   1251                              sctx->cb_cls,
   1252                              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1253                              "Selected challenge already solved");
   1254       sctx_free (sctx);
   1255       return;
   1256     }
   1257     c = find_challenge_in_cs (sctx->state,
   1258                               &cd->uuid);
   1259     GNUNET_assert (NULL != c);
   1260     if (0 == strcmp ("question",
   1261                      cd->type))
   1262     {
   1263       /* security question, answer must be a string */
   1264       json_t *janswer = json_object_get (sctx->args,
   1265                                          "answer");
   1266       const char *answer = json_string_value (janswer);
   1267 
   1268       if (NULL == answer)
   1269       {
   1270         ANASTASIS_redux_fail_ (sctx->cb,
   1271                                sctx->cb_cls,
   1272                                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1273                                "'answer' missing");
   1274         sctx_free (sctx);
   1275         return;
   1276       }
   1277       /* persist answer, in case payment is required */
   1278       GNUNET_assert (0 ==
   1279                      json_object_set (c,
   1280                                       "answer",
   1281                                       janswer));
   1282       ret = ANASTASIS_challenge_answer (ci,
   1283                                         psp,
   1284                                         timeout,
   1285                                         answer,
   1286                                         &answer_feedback_cb,
   1287                                         sctx);
   1288     }
   1289     else
   1290     {
   1291       /* Check if we got a PIN or a HASH */
   1292       json_t *pin = json_object_get (sctx->args,
   1293                                      "pin");
   1294       json_t *hash = json_object_get (sctx->args,
   1295                                       "hash");
   1296       if (json_is_integer (pin))
   1297       {
   1298         uint64_t ianswer = json_integer_value (pin);
   1299 
   1300         /* persist answer, in case async processing
   1301            happens via poll */
   1302         GNUNET_assert (0 ==
   1303                        json_object_set (c,
   1304                                         "answer-pin",
   1305                                         pin));
   1306         ret = ANASTASIS_challenge_answer2 (ci,
   1307                                            psp,
   1308                                            timeout,
   1309                                            ianswer,
   1310                                            &answer_feedback_cb,
   1311                                            sctx);
   1312       }
   1313       else if (NULL != hash)
   1314       {
   1315         struct GNUNET_HashCode hashed_answer;
   1316         struct GNUNET_JSON_Specification spec[] = {
   1317           GNUNET_JSON_spec_fixed_auto ("hash",
   1318                                        &hashed_answer),
   1319           GNUNET_JSON_spec_end ()
   1320         };
   1321 
   1322         if (GNUNET_OK !=
   1323             GNUNET_JSON_parse (sctx->args,
   1324                                spec,
   1325                                NULL, NULL))
   1326         {
   1327           ANASTASIS_redux_fail_ (sctx->cb,
   1328                                  sctx->cb_cls,
   1329                                  TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1330                                  "'answer' malformed");
   1331           sctx_free (sctx);
   1332           return;
   1333         }
   1334         ret = ANASTASIS_challenge_answer3 (ci,
   1335                                            psp,
   1336                                            timeout,
   1337                                            &hashed_answer,
   1338                                            &answer_feedback_cb,
   1339                                            sctx);
   1340       }
   1341       else
   1342       {
   1343         /* no answer provided */
   1344         ret = ANASTASIS_challenge_start (ci,
   1345                                          psp,
   1346                                          &start_feedback_cb,
   1347                                          sctx);
   1348       }
   1349     }
   1350     if (GNUNET_OK != ret)
   1351     {
   1352       ANASTASIS_redux_fail_ (sctx->cb,
   1353                              sctx->cb_cls,
   1354                              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1355                              "Failed to begin answering challenge");
   1356       sctx_free (sctx);
   1357       return;
   1358     }
   1359     return;   /* await answer feedback */
   1360   }
   1361   ANASTASIS_redux_fail_ (sctx->cb,
   1362                          sctx->cb_cls,
   1363                          TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1364                          "'uuid' not in list of challenges");
   1365   sctx_free (sctx);
   1366 }
   1367 
   1368 
   1369 /**
   1370  * Callback which passes back the recovery document and its possible
   1371  * policies. Also passes back the version of the document for the user
   1372  * to check.
   1373  *
   1374  * We find the selected challenge and try to answer it (or begin
   1375  * the process).
   1376  *
   1377  * @param cls a `struct SelectChallengeContext *`
   1378  * @param ri recovery information struct which contains the policies
   1379  */
   1380 static void
   1381 pay_challenge_cb (void *cls,
   1382                   const struct ANASTASIS_RecoveryInformation *ri)
   1383 {
   1384   struct SelectChallengeContext *sctx = cls;
   1385   json_t *challenge;
   1386 
   1387   if (NULL == ri)
   1388   {
   1389     GNUNET_break_op (0);
   1390     ANASTASIS_redux_fail_ (sctx->cb,
   1391                            sctx->cb_cls,
   1392                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1393                            "recovery information could not be deserialized");
   1394     sctx_free (sctx);
   1395     return;
   1396   }
   1397 
   1398   challenge = find_challenge_in_ri (sctx->state,
   1399                                     &sctx->uuid);
   1400   if (NULL == challenge)
   1401   {
   1402     GNUNET_break_op (0);
   1403     ANASTASIS_redux_fail_ (sctx->cb,
   1404                            sctx->cb_cls,
   1405                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1406                            "challenge not found");
   1407     sctx_free (sctx);
   1408     return;
   1409   }
   1410   /* persist payment, in case we need to run the request again */
   1411   GNUNET_assert (
   1412     0 ==
   1413     json_object_set_new (challenge,
   1414                          "payment_secret",
   1415                          GNUNET_JSON_from_data_auto (&sctx->ps)));
   1416 
   1417   for (unsigned int i = 0; i<ri->cs_len; i++)
   1418   {
   1419     struct ANASTASIS_Challenge *ci = ri->cs[i];
   1420     const struct ANASTASIS_ChallengeDetails *cd;
   1421     int ret;
   1422 
   1423     cd = ANASTASIS_challenge_get_details (ci);
   1424     if (0 !=
   1425         GNUNET_memcmp (&sctx->uuid,
   1426                        &cd->uuid))
   1427       continue;
   1428     if (cd->solved)
   1429     {
   1430       ANASTASIS_redux_fail_ (sctx->cb,
   1431                              sctx->cb_cls,
   1432                              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1433                              "Selected challenge already solved");
   1434       sctx_free (sctx);
   1435       return;
   1436     }
   1437 
   1438     if (0 == strcmp ("question",
   1439                      cd->type))
   1440     {
   1441       /* security question, answer must be a string and already ready */
   1442       json_t *janswer = json_object_get (challenge,
   1443                                          "answer");
   1444       const char *answer = json_string_value (janswer);
   1445 
   1446       if (NULL == answer)
   1447       {
   1448         ANASTASIS_redux_fail_ (sctx->cb,
   1449                                sctx->cb_cls,
   1450                                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1451                                "'answer' missing");
   1452         sctx_free (sctx);
   1453         return;
   1454       }
   1455       ret = ANASTASIS_challenge_answer (ci,
   1456                                         &sctx->ps,
   1457                                         sctx->timeout,
   1458                                         answer,
   1459                                         &answer_feedback_cb,
   1460                                         sctx);
   1461     }
   1462     else
   1463     {
   1464       ret = ANASTASIS_challenge_start (ci,
   1465                                        &sctx->ps,
   1466                                        &start_feedback_cb,
   1467                                        sctx);
   1468     }
   1469     if (GNUNET_OK != ret)
   1470     {
   1471       ANASTASIS_redux_fail_ (sctx->cb,
   1472                              sctx->cb_cls,
   1473                              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1474                              "Failed to begin answering challenge");
   1475       sctx_free (sctx);
   1476       return;
   1477     }
   1478     return;   /* await answer feedback */
   1479   }
   1480   ANASTASIS_redux_fail_ (sctx->cb,
   1481                          sctx->cb_cls,
   1482                          TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1483                          "'uuid' not in list of challenges");
   1484   sctx_free (sctx);
   1485 }
   1486 
   1487 
   1488 /**
   1489  * The user selected a challenge to be solved. Begin the solving
   1490  * process.
   1491  *
   1492  * @param[in] state we are in
   1493  * @param arguments our arguments with the solution
   1494  * @param cb functiont o call with the new state
   1495  * @param cb_cls closure for @a cb
   1496  * @return handle to cancel challenge selection step
   1497  */
   1498 static struct ANASTASIS_ReduxAction *
   1499 solve_challenge (json_t *state,
   1500                  const json_t *arguments,
   1501                  ANASTASIS_ActionCallback cb,
   1502                  void *cb_cls)
   1503 {
   1504   struct SelectChallengeContext *sctx
   1505     = GNUNET_new (struct SelectChallengeContext);
   1506   json_t *rd;
   1507   struct GNUNET_JSON_Specification spec[] = {
   1508     GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
   1509                                  &sctx->uuid),
   1510     GNUNET_JSON_spec_end ()
   1511   };
   1512 
   1513   if (NULL == arguments)
   1514   {
   1515     ANASTASIS_redux_fail_ (cb,
   1516                            cb_cls,
   1517                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1518                            "arguments missing");
   1519     return NULL;
   1520   }
   1521   if (GNUNET_OK !=
   1522       GNUNET_JSON_parse (state,
   1523                          spec,
   1524                          NULL, NULL))
   1525   {
   1526     ANASTASIS_redux_fail_ (cb,
   1527                            cb_cls,
   1528                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1529                            "'selected_challenge_uuid' missing");
   1530     return NULL;
   1531   }
   1532   rd = json_object_get (state,
   1533                         "recovery_document");
   1534   if (NULL == rd)
   1535   {
   1536     GNUNET_break_op (0);
   1537     ANASTASIS_redux_fail_ (cb,
   1538                            cb_cls,
   1539                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1540                            "solve_challenge");
   1541     return NULL;
   1542   }
   1543   sctx->cb = cb;
   1544   sctx->cb_cls = cb_cls;
   1545   sctx->state = json_incref (state);
   1546   sctx->args = json_incref ((json_t*) arguments);
   1547   sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
   1548                                             rd,
   1549                                             &solve_challenge_cb,
   1550                                             sctx,
   1551                                             &core_secret_cb,
   1552                                             sctx);
   1553   if (NULL == sctx->r)
   1554   {
   1555     json_decref (sctx->state);
   1556     json_decref (sctx->args);
   1557     GNUNET_free (sctx);
   1558     GNUNET_break_op (0);
   1559     ANASTASIS_redux_fail_ (cb,
   1560                            cb_cls,
   1561                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1562                            "'recovery_document' invalid");
   1563     return NULL;
   1564   }
   1565   sctx->ra.cleanup = &sctx_free;
   1566   sctx->ra.cleanup_cls = sctx;
   1567   return &sctx->ra;
   1568 }
   1569 
   1570 
   1571 /**
   1572  * The user asked for us to poll on pending
   1573  * asynchronous challenges to see if they have
   1574  * now completed / been satisfied.
   1575  *
   1576  * @param[in] state we are in
   1577  * @param arguments our arguments with the solution
   1578  * @param cb functiont o call with the new state
   1579  * @param cb_cls closure for @a cb
   1580  * @return handle to cancel challenge selection step
   1581  */
   1582 static struct ANASTASIS_ReduxAction *
   1583 poll_challenges (json_t *state,
   1584                  const json_t *arguments,
   1585                  ANASTASIS_ActionCallback cb,
   1586                  void *cb_cls)
   1587 {
   1588   struct SelectChallengeContext *sctx
   1589     = GNUNET_new (struct SelectChallengeContext);
   1590   json_t *rd;
   1591 
   1592   rd = json_object_get (state,
   1593                         "recovery_document");
   1594   if (NULL == rd)
   1595   {
   1596     GNUNET_break_op (0);
   1597     ANASTASIS_redux_fail_ (cb,
   1598                            cb_cls,
   1599                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1600                            "poll_challenges");
   1601     return NULL;
   1602   }
   1603   sctx->poll_only = true;
   1604   sctx->cb = cb;
   1605   sctx->cb_cls = cb_cls;
   1606   sctx->state = json_incref (state);
   1607   sctx->args = json_incref ((json_t*) arguments);
   1608   sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
   1609                                             rd,
   1610                                             &solve_challenge_cb,
   1611                                             sctx,
   1612                                             &core_secret_cb,
   1613                                             sctx);
   1614   if (NULL == sctx->r)
   1615   {
   1616     json_decref (sctx->state);
   1617     json_decref (sctx->args);
   1618     GNUNET_free (sctx);
   1619     GNUNET_break_op (0);
   1620     ANASTASIS_redux_fail_ (cb,
   1621                            cb_cls,
   1622                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1623                            "'recovery_document' invalid");
   1624     return NULL;
   1625   }
   1626   sctx->ra.cleanup = &sctx_free;
   1627   sctx->ra.cleanup_cls = sctx;
   1628   return &sctx->ra;
   1629 }
   1630 
   1631 
   1632 /**
   1633  * The user selected a challenge to be solved. Handle the payment
   1634  * process.
   1635  *
   1636  * @param[in] state we are in
   1637  * @param arguments our arguments with the solution
   1638  * @param cb functiont o call with the new state
   1639  * @param cb_cls closure for @a cb
   1640  * @return handle to cancel challenge selection step
   1641  */
   1642 static struct ANASTASIS_ReduxAction *
   1643 pay_challenge (json_t *state,
   1644                const json_t *arguments,
   1645                ANASTASIS_ActionCallback cb,
   1646                void *cb_cls)
   1647 {
   1648   struct SelectChallengeContext *sctx
   1649     = GNUNET_new (struct SelectChallengeContext);
   1650   json_t *rd;
   1651   struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
   1652   struct GNUNET_JSON_Specification spec[] = {
   1653     GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
   1654                                  &sctx->uuid),
   1655     GNUNET_JSON_spec_end ()
   1656   };
   1657   struct GNUNET_JSON_Specification aspec[] = {
   1658     GNUNET_JSON_spec_mark_optional (
   1659       GNUNET_JSON_spec_relative_time ("timeout",
   1660                                       &timeout),
   1661       NULL),
   1662     GNUNET_JSON_spec_fixed_auto ("payment_secret",
   1663                                  &sctx->ps),
   1664     GNUNET_JSON_spec_end ()
   1665   };
   1666 
   1667   if (NULL == arguments)
   1668   {
   1669     ANASTASIS_redux_fail_ (cb,
   1670                            cb_cls,
   1671                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1672                            "arguments missing");
   1673     return NULL;
   1674   }
   1675   if (GNUNET_OK !=
   1676       GNUNET_JSON_parse (arguments,
   1677                          aspec,
   1678                          NULL, NULL))
   1679   {
   1680     ANASTASIS_redux_fail_ (cb,
   1681                            cb_cls,
   1682                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1683                            "'payment_secret' missing");
   1684     return NULL;
   1685   }
   1686   if (GNUNET_OK !=
   1687       GNUNET_JSON_parse (state,
   1688                          spec,
   1689                          NULL, NULL))
   1690   {
   1691     ANASTASIS_redux_fail_ (cb,
   1692                            cb_cls,
   1693                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1694                            "'selected_challenge_uuid' missing");
   1695     return NULL;
   1696   }
   1697   rd = json_object_get (state,
   1698                         "recovery_document");
   1699   if (NULL == rd)
   1700   {
   1701     GNUNET_break_op (0);
   1702     ANASTASIS_redux_fail_ (cb,
   1703                            cb_cls,
   1704                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1705                            "pay_challenge");
   1706     return NULL;
   1707   }
   1708   sctx->timeout = timeout;
   1709   sctx->cb = cb;
   1710   sctx->cb_cls = cb_cls;
   1711   sctx->state = json_incref (state);
   1712   sctx->args = json_incref ((json_t*) arguments);
   1713   sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
   1714                                             rd,
   1715                                             &pay_challenge_cb,
   1716                                             sctx,
   1717                                             &core_secret_cb,
   1718                                             sctx);
   1719   if (NULL == sctx->r)
   1720   {
   1721     json_decref (sctx->state);
   1722     json_decref (sctx->args);
   1723     GNUNET_free (sctx);
   1724     GNUNET_break_op (0);
   1725     ANASTASIS_redux_fail_ (cb,
   1726                            cb_cls,
   1727                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1728                            "'recovery_document' invalid");
   1729     return NULL;
   1730   }
   1731   sctx->ra.cleanup = &sctx_free;
   1732   sctx->ra.cleanup_cls = sctx;
   1733   return &sctx->ra;
   1734 }
   1735 
   1736 
   1737 /**
   1738  * Callback which passes back the recovery document and its possible
   1739  * policies. Also passes back the version of the document for the user
   1740  * to check.
   1741  *
   1742  * We find the selected challenge and try to answer it (or begin
   1743  * the process).
   1744  *
   1745  * @param cls a `struct SelectChallengeContext *`
   1746  * @param ri recovery information struct which contains the policies
   1747  */
   1748 static void
   1749 select_challenge_cb (void *cls,
   1750                      const struct ANASTASIS_RecoveryInformation *ri)
   1751 {
   1752   struct SelectChallengeContext *sctx = cls;
   1753   const struct ANASTASIS_PaymentSecretP *psp = NULL;
   1754   struct ANASTASIS_PaymentSecretP ps;
   1755   struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
   1756   struct GNUNET_JSON_Specification tspec[] = {
   1757     GNUNET_JSON_spec_mark_optional (
   1758       GNUNET_JSON_spec_relative_time ("timeout",
   1759                                       &timeout),
   1760       NULL),
   1761     GNUNET_JSON_spec_end ()
   1762   };
   1763   struct GNUNET_JSON_Specification pspec[] = {
   1764     GNUNET_JSON_spec_fixed_auto ("payment_secret",
   1765                                  &ps),
   1766     GNUNET_JSON_spec_end ()
   1767   };
   1768 
   1769 
   1770   if (NULL == ri)
   1771   {
   1772     GNUNET_break_op (0);
   1773     ANASTASIS_redux_fail_ (sctx->cb,
   1774                            sctx->cb_cls,
   1775                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1776                            "recovery information could not be deserialized");
   1777     sctx_free (sctx);
   1778     return;
   1779   }
   1780 
   1781   if (GNUNET_OK !=
   1782       GNUNET_JSON_parse (sctx->args,
   1783                          tspec,
   1784                          NULL, NULL))
   1785   {
   1786     GNUNET_break_op (0);
   1787     ANASTASIS_redux_fail_ (sctx->cb,
   1788                            sctx->cb_cls,
   1789                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1790                            "'timeout' malformed");
   1791     sctx_free (sctx);
   1792     return;
   1793   }
   1794 
   1795   /* NOTE: do we need both ways to pass payment secrets? */
   1796   if (NULL !=
   1797       json_object_get (sctx->args,
   1798                        "payment_secret"))
   1799   {
   1800     /* check if we got payment secret in args */
   1801     if (GNUNET_OK !=
   1802         GNUNET_JSON_parse (sctx->args,
   1803                            pspec,
   1804                            NULL, NULL))
   1805     {
   1806       GNUNET_break_op (0);
   1807       ANASTASIS_redux_fail_ (sctx->cb,
   1808                              sctx->cb_cls,
   1809                              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1810                              "'payment_secret' malformed");
   1811       sctx_free (sctx);
   1812       return;
   1813     }
   1814     psp = &ps;
   1815   }
   1816   else
   1817   {
   1818     /* Check if we got a payment_secret in state */
   1819     json_t *challenge = find_challenge_in_ri (sctx->state,
   1820                                               &sctx->uuid);
   1821 
   1822     if (NULL == challenge)
   1823     {
   1824       GNUNET_break_op (0);
   1825       ANASTASIS_redux_fail_ (sctx->cb,
   1826                              sctx->cb_cls,
   1827                              TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1828                              "challenge not found");
   1829       sctx_free (sctx);
   1830       return;
   1831     }
   1832     if (NULL !=
   1833         json_object_get (challenge,
   1834                          "payment_secret"))
   1835     {
   1836       if (GNUNET_OK !=
   1837           GNUNET_JSON_parse (challenge,
   1838                              pspec,
   1839                              NULL, NULL))
   1840       {
   1841         GNUNET_break_op (0);
   1842         ANASTASIS_redux_fail_ (sctx->cb,
   1843                                sctx->cb_cls,
   1844                                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1845                                "'payment_secret' malformed");
   1846         sctx_free (sctx);
   1847         return;
   1848       }
   1849       psp = &ps;
   1850     }
   1851   }
   1852 
   1853   for (unsigned int i = 0; i<ri->cs_len; i++)
   1854   {
   1855     struct ANASTASIS_Challenge *ci = ri->cs[i];
   1856     const struct ANASTASIS_ChallengeDetails *cd;
   1857     int ret;
   1858 
   1859     cd = ANASTASIS_challenge_get_details (ci);
   1860     if (0 !=
   1861         GNUNET_memcmp (&sctx->uuid,
   1862                        &cd->uuid))
   1863       continue;
   1864     if (cd->solved)
   1865     {
   1866       ANASTASIS_redux_fail_ (sctx->cb,
   1867                              sctx->cb_cls,
   1868                              TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1869                              "Selected challenge already solved");
   1870       sctx_free (sctx);
   1871       return;
   1872     }
   1873     GNUNET_assert (
   1874       0 ==
   1875       json_object_set_new (sctx->state,
   1876                            "selected_challenge_uuid",
   1877                            GNUNET_JSON_from_data_auto (&cd->uuid)));
   1878     if ( (0 == strcmp ("question",
   1879                        cd->type)) ||
   1880          (0 == strcmp ("totp",
   1881                        cd->type)) )
   1882     {
   1883       /* security question or TOTP:
   1884          immediately request user to answer it */
   1885       set_state (sctx->state,
   1886                  ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
   1887       sctx->cb (sctx->cb_cls,
   1888                 TALER_EC_NONE,
   1889                 sctx->state);
   1890       sctx_free (sctx);
   1891       return;
   1892     }
   1893     /* trigger challenge */
   1894     {
   1895       json_t *c = find_challenge_in_cs (sctx->state,
   1896                                         &cd->uuid);
   1897       json_t *pin = json_object_get (c,
   1898                                      "answer-pin");
   1899 
   1900       if (NULL != pin)
   1901       {
   1902         uint64_t ianswer = json_integer_value (pin);
   1903 
   1904         ret = ANASTASIS_challenge_answer2 (ci,
   1905                                            psp,
   1906                                            timeout,
   1907                                            ianswer,
   1908                                            &answer_feedback_cb,
   1909                                            sctx);
   1910       }
   1911       else
   1912       {
   1913         ret = ANASTASIS_challenge_start (ci,
   1914                                          psp,
   1915                                          &start_feedback_cb,
   1916                                          sctx);
   1917       }
   1918     }
   1919     if (GNUNET_OK != ret)
   1920     {
   1921       ANASTASIS_redux_fail_ (sctx->cb,
   1922                              sctx->cb_cls,
   1923                              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1924                              "Failed to begin answering challenge");
   1925       sctx_free (sctx);
   1926       return;
   1927     }
   1928     return;   /* await answer feedback */
   1929   }
   1930   ANASTASIS_redux_fail_ (sctx->cb,
   1931                          sctx->cb_cls,
   1932                          TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1933                          "'uuid' not in list of challenges");
   1934   sctx_free (sctx);
   1935 }
   1936 
   1937 
   1938 /**
   1939  * The user selected a challenge to be solved. Begin the solving
   1940  * process.
   1941  *
   1942  * @param[in] state we are in
   1943  * @param arguments our arguments with the solution
   1944  * @param cb functiont o call with the new state
   1945  * @param cb_cls closure for @a cb
   1946  * @return handle to cancel challenge selection step
   1947  */
   1948 static struct ANASTASIS_ReduxAction *
   1949 select_challenge (json_t *state,
   1950                   const json_t *arguments,
   1951                   ANASTASIS_ActionCallback cb,
   1952                   void *cb_cls)
   1953 {
   1954   struct SelectChallengeContext *sctx
   1955     = GNUNET_new (struct SelectChallengeContext);
   1956   json_t *rd;
   1957   struct GNUNET_JSON_Specification spec[] = {
   1958     GNUNET_JSON_spec_fixed_auto ("uuid",
   1959                                  &sctx->uuid),
   1960     GNUNET_JSON_spec_end ()
   1961   };
   1962 
   1963   if (NULL == arguments)
   1964   {
   1965     ANASTASIS_redux_fail_ (cb,
   1966                            cb_cls,
   1967                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1968                            "arguments missing");
   1969     return NULL;
   1970   }
   1971   if (GNUNET_OK !=
   1972       GNUNET_JSON_parse (arguments,
   1973                          spec,
   1974                          NULL, NULL))
   1975   {
   1976     ANASTASIS_redux_fail_ (cb,
   1977                            cb_cls,
   1978                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   1979                            "'uuid' missing");
   1980     return NULL;
   1981   }
   1982   rd = json_object_get (state,
   1983                         "recovery_document");
   1984   if (NULL == rd)
   1985   {
   1986     GNUNET_break_op (0);
   1987     ANASTASIS_redux_fail_ (cb,
   1988                            cb_cls,
   1989                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   1990                            "select_challenge");
   1991     return NULL;
   1992   }
   1993   sctx->cb = cb;
   1994   sctx->cb_cls = cb_cls;
   1995   sctx->state = json_incref (state);
   1996   sctx->args = json_incref ((json_t*) arguments);
   1997   sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
   1998                                             rd,
   1999                                             &select_challenge_cb,
   2000                                             sctx,
   2001                                             &core_secret_cb,
   2002                                             sctx);
   2003   if (NULL == sctx->r)
   2004   {
   2005     json_decref (sctx->state);
   2006     json_decref (sctx->args);
   2007     GNUNET_free (sctx);
   2008     GNUNET_break_op (0);
   2009     ANASTASIS_redux_fail_ (cb,
   2010                            cb_cls,
   2011                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2012                            "'recovery_document' invalid");
   2013     return NULL;
   2014   }
   2015   sctx->ra.cleanup = &sctx_free;
   2016   sctx->ra.cleanup_cls = sctx;
   2017   return &sctx->ra;
   2018 }
   2019 
   2020 
   2021 /**
   2022  * The user pressed "back" during challenge solving.
   2023  * Transition back to selecting another challenge.
   2024  *
   2025  * @param[in] state we are in
   2026  * @param arguments our arguments (unused)
   2027  * @param cb functiont o call with the new state
   2028  * @param cb_cls closure for @a cb
   2029  * @return NULL (synchronous operation)
   2030  */
   2031 static struct ANASTASIS_ReduxAction *
   2032 back_challenge_solving (json_t *state,
   2033                         const json_t *arguments,
   2034                         ANASTASIS_ActionCallback cb,
   2035                         void *cb_cls)
   2036 {
   2037   (void) arguments;
   2038   GNUNET_assert (0 ==
   2039                  json_object_del (state,
   2040                                   "selected_challenge_uuid"));
   2041   set_state (state,
   2042              ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
   2043   cb (cb_cls,
   2044       TALER_EC_NONE,
   2045       state);
   2046   return NULL;
   2047 }
   2048 
   2049 
   2050 /**
   2051  * State for a "policy download" as part of a recovery operation.
   2052  */
   2053 struct PolicyDownloadEntry
   2054 {
   2055 
   2056   /**
   2057    * Redux action handle associated with this state.
   2058    */
   2059   struct ANASTASIS_ReduxAction ra;
   2060 
   2061   /**
   2062    * Backend we are querying.
   2063    */
   2064   char *backend_url;
   2065 
   2066   /**
   2067    * The /policy GET operation handle.
   2068    */
   2069   struct ANASTASIS_Recovery *recovery;
   2070 
   2071   /**
   2072    * Function to call with the result.
   2073    */
   2074   ANASTASIS_ActionCallback cb;
   2075 
   2076   /**
   2077    * Closure for @e cb.
   2078    */
   2079   void *cb_cls;
   2080 
   2081   /**
   2082    * State we are using.
   2083    */
   2084   json_t *state;
   2085 
   2086 };
   2087 
   2088 
   2089 /**
   2090  * Free @a cls data structure.
   2091  *
   2092  * @param[in] cls data structure to free, must be a `struct PolicyDownloadEntry *`
   2093  */
   2094 static void
   2095 free_pd (void *cls)
   2096 {
   2097   struct PolicyDownloadEntry *pd = cls;
   2098 
   2099   if (NULL != pd->recovery)
   2100   {
   2101     ANASTASIS_recovery_abort (pd->recovery);
   2102     pd->recovery = NULL;
   2103   }
   2104   GNUNET_free (pd->backend_url);
   2105   json_decref (pd->state);
   2106   GNUNET_free (pd);
   2107 }
   2108 
   2109 
   2110 /**
   2111  * We failed to download a policy. Show an error to the user and
   2112  * allow the user to specify alternative providers and/or policy
   2113  * versions.
   2114  *
   2115  * @param[in] pd state to fail with the policy download
   2116  * @param offline true of the reason to show is that all providers
   2117  *        were offline / did not return a salt to us
   2118  */
   2119 static void
   2120 return_no_policy (struct PolicyDownloadEntry *pd,
   2121                   bool offline)
   2122 {
   2123   enum TALER_ErrorCode ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
   2124   const char *detail = (offline)
   2125                        ? "could not contact provider (offline)"
   2126                        : "provider does not know this policy";
   2127   json_t *estate;
   2128 
   2129   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   2130               "Provider offline!\n");
   2131   estate = GNUNET_JSON_PACK (
   2132     GNUNET_JSON_pack_allow_null (
   2133       GNUNET_JSON_pack_string ("detail",
   2134                                detail)),
   2135     GNUNET_JSON_pack_uint64 ("code",
   2136                              ec),
   2137     GNUNET_JSON_pack_string ("hint",
   2138                              TALER_ErrorCode_get_hint (ec)));
   2139   pd->cb (pd->cb_cls,
   2140           ec,
   2141           estate);
   2142   free_pd (pd);
   2143 }
   2144 
   2145 
   2146 /**
   2147  * Callback which passes back the recovery document and its possible
   2148  * policies. Also passes back the version of the document for the user
   2149  * to check.
   2150  *
   2151  * Once the first policy lookup succeeds, we update our state and
   2152  * cancel all of the others, passing the obtained recovery information
   2153  * back to the user.
   2154  *
   2155  * @param cls closure for the callback with a `struct PolicyDownloadEntry *`
   2156  * @param ri recovery information struct which contains the policies
   2157  */
   2158 static void
   2159 policy_lookup_cb (void *cls,
   2160                   const struct ANASTASIS_RecoveryInformation *ri)
   2161 {
   2162   struct PolicyDownloadEntry *pd = cls;
   2163   json_t *policies;
   2164   json_t *challenges;
   2165   json_t *recovery_information;
   2166 
   2167   if (NULL == ri)
   2168   {
   2169     /* Woopsie, failed hard. */
   2170     ANASTASIS_recovery_abort (pd->recovery);
   2171     GNUNET_free (pd->backend_url);
   2172     GNUNET_free (pd);
   2173     return_no_policy (pd,
   2174                       false);
   2175     return;
   2176   }
   2177   policies = json_array ();
   2178   GNUNET_assert (NULL != policies);
   2179   for (unsigned int i = 0; i<ri->dps_len; i++)
   2180   {
   2181     struct ANASTASIS_DecryptionPolicy *dps = ri->dps[i];
   2182     json_t *pchallenges;
   2183 
   2184     pchallenges = json_array ();
   2185     GNUNET_assert (NULL != pchallenges);
   2186     for (unsigned int j = 0; j<dps->challenges_length; j++)
   2187     {
   2188       struct ANASTASIS_Challenge *c = dps->challenges[j];
   2189       const struct ANASTASIS_ChallengeDetails *cd;
   2190       json_t *cj;
   2191 
   2192       cd = ANASTASIS_challenge_get_details (c);
   2193       cj = GNUNET_JSON_PACK (
   2194         GNUNET_JSON_pack_data_auto ("uuid",
   2195                                     &cd->uuid));
   2196       GNUNET_assert (0 ==
   2197                      json_array_append_new (pchallenges,
   2198                                             cj));
   2199 
   2200     }
   2201     GNUNET_assert (0 ==
   2202                    json_array_append_new (policies,
   2203                                           pchallenges));
   2204   } /* end for all policies */
   2205   challenges = json_array ();
   2206   GNUNET_assert (NULL != challenges);
   2207   for (unsigned int i = 0; i<ri->cs_len; i++)
   2208   {
   2209     struct ANASTASIS_Challenge *c = ri->cs[i];
   2210     const struct ANASTASIS_ChallengeDetails *cd;
   2211     json_t *cj;
   2212 
   2213     cd = ANASTASIS_challenge_get_details (c);
   2214     cj = GNUNET_JSON_PACK (
   2215       GNUNET_JSON_pack_data_auto ("uuid",
   2216                                   &cd->uuid),
   2217       GNUNET_JSON_pack_string ("type",
   2218                                cd->type),
   2219       GNUNET_JSON_pack_string ("uuid-display",
   2220                                ANASTASIS_CRYPTO_uuid2s (&cd->uuid)),
   2221       GNUNET_JSON_pack_string ("instructions",
   2222                                cd->instructions));
   2223     GNUNET_assert (0 ==
   2224                    json_array_append_new (challenges,
   2225                                           cj));
   2226   } /* end for all challenges */
   2227   recovery_information = GNUNET_JSON_PACK (
   2228     GNUNET_JSON_pack_array_steal ("challenges",
   2229                                   challenges),
   2230     GNUNET_JSON_pack_array_steal ("policies",
   2231                                   policies),
   2232     GNUNET_JSON_pack_allow_null (
   2233       GNUNET_JSON_pack_string ("secret_name",
   2234                                ri->secret_name)),
   2235     GNUNET_JSON_pack_string ("provider_url",
   2236                              pd->backend_url),
   2237     GNUNET_JSON_pack_uint64 ("version",
   2238                              ri->version));
   2239   GNUNET_assert (0 ==
   2240                  json_object_set_new (pd->state,
   2241                                       "recovery_information",
   2242                                       recovery_information));
   2243   {
   2244     json_t *rd;
   2245 
   2246     rd = ANASTASIS_recovery_serialize (pd->recovery);
   2247     if (NULL == rd)
   2248     {
   2249       GNUNET_break (0);
   2250       ANASTASIS_redux_fail_ (pd->cb,
   2251                              pd->cb_cls,
   2252                              TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   2253                              "unable to serialize recovery state");
   2254       free_pd (pd);
   2255       return;
   2256     }
   2257     GNUNET_assert (0 ==
   2258                    json_object_set_new (pd->state,
   2259                                         "recovery_document",
   2260                                         rd));
   2261   }
   2262   set_state (pd->state,
   2263              ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
   2264   pd->cb (pd->cb_cls,
   2265           TALER_EC_NONE,
   2266           pd->state);
   2267   free_pd (pd);
   2268 }
   2269 
   2270 
   2271 /**
   2272  * This function is called whenever the recovery process ends.
   2273  * In this case, that should not be possible as this callback
   2274  * is used before we even begin with the challenges. So if
   2275  * we are called, it is because of some fatal error.
   2276  *
   2277  * @param cls a `struct PolicyDownloadEntry`
   2278  * @param rc error code
   2279  * @param secret contains the core secret which is passed to the user
   2280  * @param secret_size defines the size of the core secret
   2281  */
   2282 static void
   2283 core_early_secret_cb (void *cls,
   2284                       enum ANASTASIS_RecoveryStatus rc,
   2285                       const void *secret,
   2286                       size_t secret_size)
   2287 {
   2288   struct PolicyDownloadEntry *pd = cls;
   2289 
   2290   pd->recovery = NULL;
   2291   GNUNET_assert (NULL == secret);
   2292   GNUNET_assert (ANASTASIS_RS_SUCCESS != rc);
   2293   fail_by_error (pd->cb,
   2294                  pd->cb_cls,
   2295                  rc);
   2296   free_pd (pd);
   2297 }
   2298 
   2299 
   2300 /**
   2301  * DispatchHandler/Callback function which is called for a
   2302  * "next" action in "secret_selecting" state.
   2303  *
   2304  * @param state state to operate on
   2305  * @param arguments arguments to use for operation on state
   2306  * @param cb callback to call during/after operation
   2307  * @param cb_cls callback closure
   2308  * @return NULL
   2309  */
   2310 static struct ANASTASIS_ReduxAction *
   2311 done_secret_selecting (json_t *state,
   2312                        const json_t *arguments,
   2313                        ANASTASIS_ActionCallback cb,
   2314                        void *cb_cls)
   2315 {
   2316   uint32_t mask;
   2317   const json_t *pa;
   2318   struct GNUNET_JSON_Specification spec[] = {
   2319     GNUNET_JSON_spec_uint32 ("attribute_mask",
   2320                              &mask),
   2321     GNUNET_JSON_spec_array_const ("providers",
   2322                                   &pa),
   2323     GNUNET_JSON_spec_end ()
   2324   };
   2325   struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
   2326   struct GNUNET_JSON_Specification pspec[] = {
   2327     GNUNET_JSON_spec_fixed_auto ("provider_salt",
   2328                                  &provider_salt),
   2329     GNUNET_JSON_spec_end ()
   2330   };
   2331   json_t *p_cfg;
   2332   json_t *id_data;
   2333   const json_t *providers;
   2334 
   2335   if (GNUNET_OK !=
   2336       GNUNET_JSON_parse (arguments,
   2337                          spec,
   2338                          NULL, NULL))
   2339   {
   2340     GNUNET_break (0);
   2341     json_dumpf (arguments,
   2342                 stderr,
   2343                 JSON_INDENT (2));
   2344     ANASTASIS_redux_fail_ (cb,
   2345                            cb_cls,
   2346                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   2347                            NULL);
   2348     return NULL;
   2349   }
   2350   providers = json_object_get (state,
   2351                                "authentication_providers");
   2352   if ( (NULL == providers) ||
   2353        (! json_is_object (providers)) )
   2354   {
   2355     GNUNET_break (0);
   2356     ANASTASIS_redux_fail_ (cb,
   2357                            cb_cls,
   2358                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2359                            "'authentication_providers' missing");
   2360     return NULL;
   2361   }
   2362 
   2363   {
   2364     size_t poff;
   2365     json_t *pe;
   2366     uint64_t version;
   2367     const char *provider_url;
   2368 
   2369     json_array_foreach (pa, poff, pe)
   2370     {
   2371       struct GNUNET_JSON_Specification ispec[] = {
   2372         GNUNET_JSON_spec_uint64 ("version",
   2373                                  &version),
   2374         GNUNET_JSON_spec_string ("url",
   2375                                  &provider_url),
   2376         GNUNET_JSON_spec_end ()
   2377       };
   2378 
   2379       if (GNUNET_OK !=
   2380           GNUNET_JSON_parse (pe,
   2381                              ispec,
   2382                              NULL, NULL))
   2383       {
   2384         GNUNET_break (0);
   2385         json_dumpf (pe,
   2386                     stderr,
   2387                     JSON_INDENT (2));
   2388         ANASTASIS_redux_fail_ (cb,
   2389                                cb_cls,
   2390                                TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   2391                                NULL);
   2392         return NULL;
   2393       }
   2394 
   2395       p_cfg = json_object_get (providers,
   2396                                provider_url);
   2397       if (MHD_HTTP_OK !=
   2398           json_integer_value (json_object_get (p_cfg,
   2399                                                "http_status")))
   2400         continue;
   2401       if (GNUNET_OK !=
   2402           GNUNET_JSON_parse (p_cfg,
   2403                              pspec,
   2404                              NULL, NULL))
   2405       {
   2406         GNUNET_break (0); /* should be impossible for well-formed state */
   2407         ANASTASIS_redux_fail_ (cb,
   2408                                cb_cls,
   2409                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2410                                "Salt unknown for selected provider");
   2411         return NULL;
   2412       }
   2413       id_data = json_object_get (state,
   2414                                  "identity_attributes");
   2415       if (NULL == id_data)
   2416       {
   2417         GNUNET_break (0); /* should be impossible for well-formed state */
   2418         ANASTASIS_redux_fail_ (cb,
   2419                                cb_cls,
   2420                                TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2421                                "'identity_attributes' missing");
   2422         return NULL;
   2423       }
   2424       {
   2425         struct PolicyDownloadEntry *pd
   2426           = GNUNET_new (struct PolicyDownloadEntry);
   2427 
   2428         pd->cb = cb;
   2429         pd->cb_cls = cb_cls;
   2430         pd->state = json_incref (state);
   2431         pd->backend_url = GNUNET_strdup (provider_url);
   2432         pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_,
   2433                                                  id_data,
   2434                                                  version,
   2435                                                  pd->backend_url,
   2436                                                  &provider_salt,
   2437                                                  &policy_lookup_cb,
   2438                                                  pd,
   2439                                                  &core_early_secret_cb,
   2440                                                  pd);
   2441         if (NULL == pd->recovery)
   2442         {
   2443           GNUNET_break (0);
   2444           ANASTASIS_redux_fail_ (cb,
   2445                                  cb_cls,
   2446                                  TALER_EC_ANASTASIS_REDUCER_INTERNAL_ERROR,
   2447                                  NULL);
   2448           GNUNET_free (pd->backend_url);
   2449           json_decref (pd->state);
   2450           GNUNET_free (pd);
   2451           return NULL;
   2452         }
   2453         pd->ra.cleanup = &free_pd;
   2454         pd->ra.cleanup_cls = pd;
   2455         return &pd->ra;
   2456       }
   2457     }
   2458   }
   2459 
   2460   /* no provider worked */
   2461   ANASTASIS_redux_fail_ (cb,
   2462                          cb_cls,
   2463                          TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   2464                          "selected provider is not online");
   2465   return NULL;
   2466 }
   2467 
   2468 
   2469 /**
   2470  * The user wants us to add another provider. Download /config.
   2471  *
   2472  * @param[in] state we are in
   2473  * @param arguments our arguments with the solution
   2474  * @param cb function to call with the new state
   2475  * @param cb_cls closure for @a cb
   2476  * @return handle to cancel challenge selection step
   2477  */
   2478 static struct ANASTASIS_ReduxAction *
   2479 add_provider (json_t *state,
   2480               const json_t *arguments,
   2481               ANASTASIS_ActionCallback cb,
   2482               void *cb_cls)
   2483 {
   2484   const char *provider_url;
   2485   struct GNUNET_JSON_Specification spec[] = {
   2486     GNUNET_JSON_spec_string ("provider_url",
   2487                              &provider_url),
   2488     GNUNET_JSON_spec_end ()
   2489   };
   2490 
   2491   if (GNUNET_OK !=
   2492       GNUNET_JSON_parse (arguments,
   2493                          spec,
   2494                          NULL, NULL))
   2495   {
   2496     GNUNET_break (0);
   2497     ANASTASIS_redux_fail_ (cb,
   2498                            cb_cls,
   2499                            TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
   2500                            NULL);
   2501     return NULL;
   2502   }
   2503   return ANASTASIS_REDUX_add_provider_to_state_ (provider_url,
   2504                                                  state,
   2505                                                  cb,
   2506                                                  cb_cls);
   2507 }
   2508 
   2509 
   2510 /**
   2511  * Signature of callback function that implements a state transition.
   2512  *
   2513  *  @param state current state
   2514  *  @param arguments arguments for the state transition
   2515  *  @param cb function to call when done
   2516  *  @param cb_cls closure for @a cb
   2517  */
   2518 typedef struct ANASTASIS_ReduxAction *
   2519 (*DispatchHandler)(json_t *state,
   2520                    const json_t *arguments,
   2521                    ANASTASIS_ActionCallback cb,
   2522                    void *cb_cls);
   2523 
   2524 
   2525 struct ANASTASIS_ReduxAction *
   2526 ANASTASIS_recovery_action_ (json_t *state,
   2527                             const char *action,
   2528                             const json_t *arguments,
   2529                             ANASTASIS_ActionCallback cb,
   2530                             void *cb_cls)
   2531 {
   2532   struct Dispatcher
   2533   {
   2534     enum ANASTASIS_RecoveryState recovery_state;
   2535     const char *recovery_action;
   2536     DispatchHandler fun;
   2537   } dispatchers[] = {
   2538     {
   2539       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2540       "add_provider",
   2541       &add_provider
   2542     },
   2543     {
   2544       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2545       "poll_providers",
   2546       &ANASTASIS_REDUX_poll_providers_
   2547     },
   2548     {
   2549       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2550       "select_version",
   2551       &done_secret_selecting
   2552     },
   2553     {
   2554       ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
   2555       "back",
   2556       &ANASTASIS_back_generic_decrement_
   2557     },
   2558     {
   2559       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2560       "select_challenge",
   2561       &select_challenge
   2562     },
   2563     {
   2564       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2565       "sync_providers",
   2566       &ANASTASIS_REDUX_sync_providers_
   2567     },
   2568     {
   2569       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2570       "poll",
   2571       &poll_challenges
   2572     },
   2573     {
   2574       ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
   2575       "back",
   2576       &ANASTASIS_back_generic_decrement_
   2577     },
   2578     {
   2579       ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
   2580       "pay",
   2581       &pay_challenge
   2582     },
   2583     {
   2584       ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
   2585       "back",
   2586       &ANASTASIS_back_generic_decrement_
   2587     },
   2588     {
   2589       ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
   2590       "solve_challenge",
   2591       &solve_challenge
   2592     },
   2593     {
   2594       ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
   2595       "back",
   2596       &back_challenge_solving
   2597     },
   2598     { ANASTASIS_RECOVERY_STATE_INVALID, NULL, NULL }
   2599   };
   2600   const char *s = json_string_value (json_object_get (state,
   2601                                                       "recovery_state"));
   2602   enum ANASTASIS_RecoveryState rs;
   2603 
   2604   GNUNET_assert (NULL != s);
   2605   rs = ANASTASIS_recovery_state_from_string_ (s);
   2606   if (ANASTASIS_RECOVERY_STATE_INVALID == rs)
   2607   {
   2608     ANASTASIS_redux_fail_ (cb,
   2609                            cb_cls,
   2610                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2611                            "'recovery_state' field invalid");
   2612     return NULL;
   2613   }
   2614   for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
   2615   {
   2616     if ( (rs == dispatchers[i].recovery_state) &&
   2617          (0 == strcmp (action,
   2618                        dispatchers[i].recovery_action)) )
   2619     {
   2620       return dispatchers[i].fun (state,
   2621                                  arguments,
   2622                                  cb,
   2623                                  cb_cls);
   2624     }
   2625   }
   2626   ANASTASIS_redux_fail_ (cb,
   2627                          cb_cls,
   2628                          TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
   2629                          action);
   2630   return NULL;
   2631 }
   2632 
   2633 
   2634 struct ANASTASIS_ReduxAction *
   2635 ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
   2636                                            const json_t *arguments,
   2637                                            ANASTASIS_ActionCallback cb,
   2638                                            void *cb_cls)
   2639 {
   2640   const json_t *providers;
   2641   json_t *attributes;
   2642 
   2643   providers = json_object_get (state,
   2644                                "authentication_providers");
   2645   if ( (NULL == providers) ||
   2646        (! json_is_object (providers)) )
   2647   {
   2648     GNUNET_break (0);
   2649     ANASTASIS_redux_fail_ (cb,
   2650                            cb_cls,
   2651                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2652                            "'authentication_providers' missing");
   2653     return NULL;
   2654   }
   2655   attributes = json_object_get (arguments,
   2656                                 "identity_attributes");
   2657   if ( (NULL == attributes) ||
   2658        (! json_is_object (attributes)) )
   2659   {
   2660     GNUNET_break (0);
   2661     ANASTASIS_redux_fail_ (cb,
   2662                            cb_cls,
   2663                            TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
   2664                            "'identity_attributes' missing");
   2665     return NULL;
   2666   }
   2667   GNUNET_assert (0 ==
   2668                  json_object_set (state,
   2669                                   "identity_attributes",
   2670                                   attributes));
   2671   set_state (state,
   2672              ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
   2673   cb (cb_cls,
   2674       TALER_EC_NONE,
   2675       state);
   2676   return NULL;
   2677 }