anastasis

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

do_verify_challenge_code.c (6998B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020-2022 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file stasis/do_verify_challenge_code.c
     18  * @brief Anastasis database: do verify challenge code
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "anastasis-db_pg.h"
     23 #include "anastasis/anastasis-database/do_verify_challenge_code.h"
     24 #include "anastasis/anastasis-database/transaction.h"
     25 #include "anastasis/anastasis-database/preflight.h"
     26 #include <taler/taler_pq_lib.h>
     27 
     28 
     29 struct CheckValidityContext
     30 {
     31   /**
     32    * Code to check for.
     33    */
     34   const struct GNUNET_HashCode *hashed_code;
     35 
     36   /**
     37    * Truth we are processing.
     38    */
     39   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid;
     40 
     41   /**
     42    * Set to the matching challenge code (if @e valid).
     43    */
     44   uint64_t code;
     45 
     46   /**
     47    * Set to true if a code matching @e hashed_code was found.
     48    */
     49   bool valid;
     50 
     51   /**
     52    * Set to true if a code matching @e hashed_code was set to 'satisfied' by the plugin.
     53    */
     54   bool satisfied;
     55 
     56   /**
     57    * Set to true if we had a database failure.
     58    */
     59   bool db_failure;
     60 
     61 };
     62 
     63 
     64 /**
     65  * Helper function for #postgres_verify_challenge_code().
     66  * To be called with the results of a SELECT statement
     67  * that has returned @a num_results results.
     68  *
     69  * @param cls closure of type `struct CheckValidityContext *`
     70  * @param result the postgres result
     71  * @param num_results the number of results in @a result
     72  */
     73 static void
     74 check_valid_code (void *cls,
     75                   PGresult *result,
     76                   unsigned int num_results)
     77 {
     78   struct CheckValidityContext *cvc = cls;
     79 
     80   for (unsigned int i = 0; i < num_results; i++)
     81   {
     82     uint64_t server_code;
     83     uint8_t sat;
     84     struct GNUNET_PQ_ResultSpec rs[] = {
     85       GNUNET_PQ_result_spec_uint64 ("code",
     86                                     &server_code),
     87       GNUNET_PQ_result_spec_auto_from_type ("satisfied",
     88                                             &sat),
     89       GNUNET_PQ_result_spec_end
     90     };
     91 
     92     if (GNUNET_OK !=
     93         GNUNET_PQ_extract_result (result,
     94                                   rs,
     95                                   i))
     96     {
     97       GNUNET_break (0);
     98       cvc->db_failure = true;
     99       return;
    100     }
    101     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    102                 "Found issued challenge %llu (client: %s)\n",
    103                 (unsigned long long) server_code,
    104                 GNUNET_h2s (cvc->hashed_code));
    105     {
    106       struct GNUNET_HashCode shashed_code;
    107 
    108       ANASTASIS_hash_answer (server_code,
    109                              &shashed_code);
    110       if (0 ==
    111           GNUNET_memcmp (&shashed_code,
    112                          cvc->hashed_code))
    113       {
    114         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    115                     "Challenge is valid challenge (%s)\n",
    116                     (0 != sat) ? "satisfied" : "not satisfied");
    117         cvc->valid = true;
    118         cvc->code = server_code;
    119         cvc->satisfied = (0 != sat);
    120       }
    121       else
    122       {
    123         /* count failures to prevent brute-force attacks */
    124         struct GNUNET_PQ_QueryParam params[] = {
    125           GNUNET_PQ_query_param_auto_from_type (cvc->truth_uuid),
    126           GNUNET_PQ_query_param_uint64 (&server_code),
    127           GNUNET_PQ_query_param_end
    128         };
    129         enum GNUNET_DB_QueryStatus qs;
    130 
    131         qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
    132                                                  "do_verify_challenge_code_update_retry_counter",
    133                                                  params);
    134         if (qs <= 0)
    135         {
    136           GNUNET_break (0);
    137           cvc->db_failure = true;
    138         }
    139       }
    140     }
    141   }
    142 }
    143 
    144 
    145 /**
    146  * Verify the provided code with the code on the server.
    147  * If the code matches the function will return with success, if the code
    148  * does not match, the retry counter will be decreased by one.
    149  *
    150  * @param truth_uuid identification of the challenge which the code corresponds to
    151  * @param hashed_code code which the user provided and wants to verify
    152  * @param[out] code set to the original numeric code
    153  * @param[out] satisfied set to true if the challenge is set to satisfied
    154  * @return code validity status
    155  */
    156 enum ANASTASIS_DB_CodeStatus
    157 ANASTASIS_DB_do_verify_challenge_code (
    158   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
    159   const struct GNUNET_HashCode *hashed_code,
    160   uint64_t *code,
    161   bool *satisfied)
    162 {
    163   struct CheckValidityContext cvc = {
    164     .truth_uuid = truth_uuid,
    165     .hashed_code = hashed_code,
    166   };
    167   struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
    168   struct GNUNET_PQ_QueryParam params[] = {
    169     GNUNET_PQ_query_param_auto_from_type (truth_uuid),
    170     GNUNET_PQ_query_param_timestamp (&now),
    171     GNUNET_PQ_query_param_end
    172   };
    173   enum GNUNET_DB_QueryStatus qs;
    174 
    175   *satisfied = false;
    176   PREPARE ("do_verify_challenge_code_update_retry_counter",
    177            "UPDATE anastasis_challengecode"
    178            " SET retry_counter=retry_counter - 1"
    179            " WHERE truth_uuid=$1"
    180            "   AND code=$2"
    181            "   AND retry_counter != 0;");
    182   /* Only the newest code is answerable.  A truth can have several unexpired
    183      codes at once, and without the LIMIT every one of them that does not
    184      match burns a retry -- so a single wrong guess could exhaust the counter,
    185      and even a correct answer cost retries for its siblings.  This matches
    186      what insert_challenge_code.c already assumes when it issues a code. */
    187   PREPARE ("do_verify_challenge_code_select",
    188            "SELECT "
    189            " code"
    190            ",satisfied"
    191            " FROM anastasis_challengecode"
    192            " WHERE truth_uuid=$1"
    193            "   AND expiration_date > $2"
    194            "   AND retry_counter != 0"
    195            " ORDER BY creation_date DESC"
    196            " LIMIT 1;");
    197   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
    198                                              "do_verify_challenge_code_select",
    199                                              params,
    200                                              &check_valid_code,
    201                                              &cvc);
    202   if ( (qs < 0) ||
    203        (cvc.db_failure) )
    204     return ANASTASIS_DB_CODE_STATUS_HARD_ERROR;
    205   *code = cvc.code;
    206   if (cvc.valid)
    207   {
    208     *satisfied = cvc.satisfied;
    209     return ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED;
    210   }
    211   if (0 == qs)
    212     return ANASTASIS_DB_CODE_STATUS_NO_RESULTS;
    213   return ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH;
    214 }
    215 
    216 
    217 /* end of do_verify_challenge_code.c */