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 */