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