taler-exchange-httpd_reserves_attest.c (12353B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2022, 2024 Taler Systems SA 4 5 TALER 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 TALER 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 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_reserves_attest.c 18 * @brief Handle /reserves/$RESERVE_PUB/attest requests 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <jansson.h> 26 #include "taler/taler_dbevents.h" 27 #include "taler/taler_kyclogic_lib.h" 28 #include "taler/taler_json_lib.h" 29 #include "taler/taler_mhd_lib.h" 30 #include "taler-exchange-httpd_keys.h" 31 #include "taler-exchange-httpd_reserves_attest.h" 32 #include "taler-exchange-httpd_responses.h" 33 34 35 /** 36 * How far do we allow a client's time to be off when 37 * checking the request timestamp? 38 */ 39 #define TIMESTAMP_TOLERANCE \ 40 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) 41 42 43 /** 44 * Closure for #reserve_attest_transaction. 45 */ 46 struct ReserveAttestContext 47 { 48 /** 49 * Public key of the reserve the inquiry is about. 50 */ 51 struct TALER_ReservePublicKeyP reserve_pub; 52 53 /** 54 * Hash of the payto URI of this reserve. 55 */ 56 struct TALER_NormalizedPaytoHashP h_payto; 57 58 /** 59 * Timestamp of the request. 60 */ 61 struct GNUNET_TIME_Timestamp timestamp; 62 63 /** 64 * Expiration time for the attestation. 65 */ 66 struct GNUNET_TIME_Timestamp etime; 67 68 /** 69 * List of requested details. 70 */ 71 const json_t *details; 72 73 /** 74 * Client signature approving the request. 75 */ 76 struct TALER_ReserveSignatureP reserve_sig; 77 78 /** 79 * Attributes we are affirming. JSON object. 80 */ 81 json_t *json_attest; 82 83 /** 84 * Database error codes encountered. 85 */ 86 enum GNUNET_DB_QueryStatus qs; 87 88 /** 89 * Set to true if we did not find the reserve. 90 */ 91 bool not_found; 92 93 }; 94 95 96 /** 97 * Send reserve attest to client. 98 * 99 * @param connection connection to the client 100 * @param rhc reserve attest to return 101 * @return MHD result code 102 */ 103 static MHD_RESULT 104 reply_reserve_attest_success (struct MHD_Connection *connection, 105 const struct ReserveAttestContext *rhc) 106 { 107 struct TALER_ExchangeSignatureP exchange_sig; 108 struct TALER_ExchangePublicKeyP exchange_pub; 109 enum TALER_ErrorCode ec; 110 struct GNUNET_TIME_Timestamp now; 111 112 if (NULL == rhc->json_attest) 113 { 114 GNUNET_break (0); 115 return TALER_MHD_reply_with_error (connection, 116 MHD_HTTP_INTERNAL_SERVER_ERROR, 117 TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, 118 NULL); 119 } 120 now = GNUNET_TIME_timestamp_get (); 121 ec = TALER_exchange_online_reserve_attest_details_sign ( 122 &TEH_keys_exchange_sign_, 123 now, 124 rhc->etime, 125 &rhc->reserve_pub, 126 rhc->json_attest, 127 &exchange_pub, 128 &exchange_sig); 129 if (TALER_EC_NONE != ec) 130 { 131 GNUNET_break (0); 132 return TALER_MHD_reply_with_ec (connection, 133 ec, 134 NULL); 135 } 136 return TALER_MHD_REPLY_JSON_PACK ( 137 connection, 138 MHD_HTTP_OK, 139 GNUNET_JSON_pack_data_auto ("exchange_sig", 140 &exchange_sig), 141 GNUNET_JSON_pack_data_auto ("exchange_pub", 142 &exchange_pub), 143 GNUNET_JSON_pack_timestamp ("exchange_timestamp", 144 now), 145 GNUNET_JSON_pack_timestamp ("expiration_time", 146 rhc->etime), 147 GNUNET_JSON_pack_object_steal ("attributes", 148 rhc->json_attest)); 149 } 150 151 152 /** 153 * Function called with information about all applicable 154 * legitimization processes for the given user. Finds the 155 * available attributes and merges them into our result 156 * set based on the details requested by the client. 157 * 158 * @param cls our `struct ReserveAttestContext *` 159 * @param h_payto account for which the attribute data is stored 160 * @param provider_name provider that must be checked 161 * @param collection_time when was the data collected 162 * @param expiration_time when does the data expire 163 * @param enc_attributes_size number of bytes in @a enc_attributes 164 * @param enc_attributes encrypted attribute data 165 */ 166 static void 167 kyc_process_cb (void *cls, 168 const struct TALER_NormalizedPaytoHashP *h_payto, 169 const char *provider_name, 170 struct GNUNET_TIME_Timestamp collection_time, 171 struct GNUNET_TIME_Timestamp expiration_time, 172 size_t enc_attributes_size, 173 const void *enc_attributes) 174 { 175 struct ReserveAttestContext *rsc = cls; 176 json_t *attrs; 177 json_t *val; 178 const char *name; 179 bool match = false; 180 181 if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time)) 182 return; 183 attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key, 184 enc_attributes, 185 enc_attributes_size); 186 if (NULL == attrs) 187 { 188 GNUNET_break (0); 189 return; 190 } 191 json_object_foreach (attrs, name, val) 192 { 193 bool requested = strcmp (name, 194 "FORM_ID"); /* we always return the FORM_ID */ 195 size_t idx; 196 json_t *str; 197 198 if (NULL != json_object_get (rsc->json_attest, 199 name)) 200 continue; /* duplicate */ 201 json_array_foreach (rsc->details, idx, str) 202 { 203 if (0 == strcmp (json_string_value (str), 204 name)) 205 { 206 requested = true; 207 break; 208 } 209 } 210 if (! requested) 211 { 212 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 213 "Skipping attribute `%s': not requested\n", 214 name); 215 continue; 216 } 217 match = true; 218 GNUNET_assert (0 == 219 json_object_set (rsc->json_attest, /* NOT set_new! */ 220 name, 221 val)); 222 } 223 json_decref (attrs); 224 if (! match) 225 return; 226 rsc->etime = GNUNET_TIME_timestamp_min (expiration_time, 227 rsc->etime); 228 } 229 230 231 /** 232 * Function implementing /reserves/$RID/attest transaction. Given the public 233 * key of a reserve, return the associated transaction attest. Runs the 234 * transaction logic; IF it returns a non-error code, the transaction logic 235 * MUST NOT queue a MHD response. IF it returns an hard error, the 236 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it 237 * returns the soft error code, the function MAY be called again to retry and 238 * MUST not queue a MHD response. 239 * 240 * @param cls a `struct ReserveAttestContext *` 241 * @param connection MHD request which triggered the transaction 242 * @param[out] mhd_ret set to MHD response status for @a connection, 243 * if transaction failed (!); unused 244 * @return transaction status 245 */ 246 static enum GNUNET_DB_QueryStatus 247 reserve_attest_transaction (void *cls, 248 struct MHD_Connection *connection, 249 MHD_RESULT *mhd_ret) 250 { 251 struct ReserveAttestContext *rsc = cls; 252 enum GNUNET_DB_QueryStatus qs; 253 254 rsc->json_attest = json_object (); 255 GNUNET_assert (NULL != rsc->json_attest); 256 qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls, 257 &rsc->h_payto, 258 &kyc_process_cb, 259 rsc); 260 switch (qs) 261 { 262 case GNUNET_DB_STATUS_HARD_ERROR: 263 GNUNET_break (0); 264 *mhd_ret 265 = TALER_MHD_reply_with_error (connection, 266 MHD_HTTP_INTERNAL_SERVER_ERROR, 267 TALER_EC_GENERIC_DB_FETCH_FAILED, 268 "select_kyc_attributes"); 269 return qs; 270 case GNUNET_DB_STATUS_SOFT_ERROR: 271 GNUNET_break (0); 272 return qs; 273 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 274 rsc->not_found = true; 275 return qs; 276 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 277 rsc->not_found = false; 278 break; 279 } 280 return qs; 281 } 282 283 284 MHD_RESULT 285 TEH_handler_reserves_attest (struct TEH_RequestContext *rc, 286 const json_t *root, 287 const char *const args[1]) 288 { 289 struct ReserveAttestContext rsc = { 290 .etime = GNUNET_TIME_UNIT_FOREVER_TS 291 }; 292 MHD_RESULT mhd_ret; 293 struct GNUNET_JSON_Specification spec[] = { 294 GNUNET_JSON_spec_timestamp ("request_timestamp", 295 &rsc.timestamp), 296 GNUNET_JSON_spec_array_const ("details", 297 &rsc.details), 298 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 299 &rsc.reserve_sig), 300 GNUNET_JSON_spec_end () 301 }; 302 struct GNUNET_TIME_Timestamp now; 303 304 if (GNUNET_OK != 305 GNUNET_STRINGS_string_to_data (args[0], 306 strlen (args[0]), 307 &rsc.reserve_pub, 308 sizeof (rsc.reserve_pub))) 309 { 310 GNUNET_break_op (0); 311 return TALER_MHD_reply_with_error (rc->connection, 312 MHD_HTTP_BAD_REQUEST, 313 TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, 314 args[0]); 315 } 316 { 317 enum GNUNET_GenericReturnValue res; 318 319 res = TALER_MHD_parse_json_data (rc->connection, 320 root, 321 spec); 322 if (GNUNET_SYSERR == res) 323 { 324 GNUNET_break (0); 325 return MHD_NO; /* hard failure */ 326 } 327 if (GNUNET_NO == res) 328 { 329 GNUNET_break_op (0); 330 return MHD_YES; /* failure */ 331 } 332 } 333 now = GNUNET_TIME_timestamp_get (); 334 if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, 335 rsc.timestamp.abs_time, 336 TIMESTAMP_TOLERANCE)) 337 { 338 GNUNET_break_op (0); 339 return TALER_MHD_reply_with_error (rc->connection, 340 MHD_HTTP_BAD_REQUEST, 341 TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, 342 NULL); 343 } 344 345 if (GNUNET_OK != 346 TALER_wallet_reserve_attest_request_verify (rsc.timestamp, 347 rsc.details, 348 &rsc.reserve_pub, 349 &rsc.reserve_sig)) 350 { 351 GNUNET_break_op (0); 352 return TALER_MHD_reply_with_error (rc->connection, 353 MHD_HTTP_FORBIDDEN, 354 TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE, 355 NULL); 356 } 357 358 { 359 struct TALER_NormalizedPayto payto_uri; 360 361 payto_uri = TALER_reserve_make_payto (TEH_base_url, 362 &rsc.reserve_pub); 363 TALER_normalized_payto_hash (payto_uri, 364 &rsc.h_payto); 365 GNUNET_free (payto_uri.normalized_payto); 366 } 367 368 if (GNUNET_OK != 369 TEH_DB_run_transaction (rc->connection, 370 "post reserve attest", 371 TEH_MT_REQUEST_OTHER, 372 &mhd_ret, 373 &reserve_attest_transaction, 374 &rsc)) 375 { 376 return mhd_ret; 377 } 378 if (rsc.not_found) 379 { 380 json_decref (rsc.json_attest); 381 return TALER_MHD_reply_with_error (rc->connection, 382 MHD_HTTP_NOT_FOUND, 383 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 384 args[0]); 385 } 386 return reply_reserve_attest_success (rc->connection, 387 &rsc); 388 } 389 390 391 /* end of taler-exchange-httpd_reserves_attest.c */