taler-auditor-httpd_deposit-confirmation.c (16001B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 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-auditor-httpd_deposit-confirmation.c 18 * @brief Handle /deposit-confirmation requests; parses the POST and JSON and 19 * verifies the coin signature before handing things off 20 * to the database. 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_json_lib.h> 26 #include <jansson.h> 27 #include <microhttpd.h> 28 #include <pthread.h> 29 #include "taler/taler_json_lib.h" 30 #include "taler/taler_mhd_lib.h" 31 #include "taler-auditor-httpd.h" 32 #include "taler-auditor-httpd_deposit-confirmation.h" 33 34 GNUNET_NETWORK_STRUCT_BEGIN 35 36 /** 37 * @brief Information about a signing key of the exchange. Signing keys are used 38 * to sign exchange messages other than coins, i.e. to confirm that a 39 * deposit was successful or that a refresh was accepted. 40 */ 41 struct ExchangeSigningKeyDataP 42 { 43 44 /** 45 * When does this signing key begin to be valid? 46 */ 47 struct GNUNET_TIME_TimestampNBO start; 48 49 /** 50 * When does this signing key expire? Note: This is currently when 51 * the Exchange will definitively stop using it. Signatures made with 52 * the key remain valid until @e end. When checking validity periods, 53 * clients should allow for some overlap between keys and tolerate 54 * the use of either key during the overlap time (due to the 55 * possibility of clock skew). 56 */ 57 struct GNUNET_TIME_TimestampNBO expire; 58 59 /** 60 * When do signatures with this signing key become invalid? After 61 * this point, these signatures cannot be used in (legal) disputes 62 * anymore, as the Exchange is then allowed to destroy its side of the 63 * evidence. @e end is expected to be significantly larger than @e 64 * expire (by a year or more). 65 */ 66 struct GNUNET_TIME_TimestampNBO end; 67 68 /** 69 * The public online signing key that the exchange will use 70 * between @e start and @e expire. 71 */ 72 struct TALER_ExchangePublicKeyP signkey_pub; 73 }; 74 75 GNUNET_NETWORK_STRUCT_END 76 77 78 /** 79 * Cache of already verified exchange signing keys. Maps the hash of the 80 * `struct TALER_ExchangeSigningKeyValidityPS` to the (static) string 81 * "verified" or "revoked". Access to this map is guarded by the #lock. 82 */ 83 static struct GNUNET_CONTAINER_MultiHashMap *cache; 84 85 /** 86 * Lock for operations on #cache. 87 */ 88 static pthread_mutex_t lock; 89 90 91 /** 92 * We have parsed the JSON information about the deposit, do some 93 * basic sanity checks (especially that the signature on the coin is 94 * valid, and that this type of coin exists) and then execute the 95 * deposit. 96 * 97 * @param connection the MHD connection to handle 98 * @param dc information about the deposit confirmation 99 * @param es information about the exchange's signing key 100 * @return MHD result code 101 */ 102 static MHD_RESULT 103 verify_and_execute_deposit_confirmation ( 104 struct MHD_Connection *connection, 105 const struct TALER_AUDITORDB_DepositConfirmation *dc, 106 const struct TALER_AUDITORDB_ExchangeSigningKey *es) 107 { 108 enum GNUNET_DB_QueryStatus qs; 109 struct GNUNET_HashCode h; 110 const char *cached; 111 struct ExchangeSigningKeyDataP skv = { 112 .start = GNUNET_TIME_timestamp_hton (es->ep_start), 113 .expire = GNUNET_TIME_timestamp_hton (es->ep_expire), 114 .end = GNUNET_TIME_timestamp_hton (es->ep_end), 115 .signkey_pub = es->exchange_pub 116 }; 117 const struct TALER_CoinSpendSignatureP *coin_sigps[ 118 GNUNET_NZL (dc->num_coins)]; 119 120 for (unsigned int i = 0; i < dc->num_coins; i++) 121 coin_sigps[i] = &dc->coin_sigs[i]; 122 123 if (GNUNET_TIME_absolute_is_future (es->ep_start.abs_time) || 124 GNUNET_TIME_absolute_is_past (es->ep_expire.abs_time)) 125 { 126 /* Signing key expired */ 127 TALER_LOG_WARNING ("Expired exchange signing key\n"); 128 return TALER_MHD_reply_with_error (connection, 129 MHD_HTTP_FORBIDDEN, 130 TALER_EC_AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID, 131 "master signature expired"); 132 } 133 134 /* check our cache */ 135 GNUNET_CRYPTO_hash (&skv, 136 sizeof(skv), 137 &h); 138 GNUNET_assert (0 == pthread_mutex_lock (&lock)); 139 cached = GNUNET_CONTAINER_multihashmap_get (cache, 140 &h); 141 GNUNET_assert (0 == pthread_mutex_unlock (&lock)); 142 if (GNUNET_SYSERR == 143 TAH_plugin->preflight (TAH_plugin->cls)) 144 { 145 GNUNET_break (0); 146 return TALER_MHD_reply_with_error (connection, 147 MHD_HTTP_INTERNAL_SERVER_ERROR, 148 TALER_EC_GENERIC_DB_SETUP_FAILED, 149 NULL); 150 } 151 if (NULL == cached) 152 { 153 /* Not in cache, need to verify the signature, persist it, and possibly cache it */ 154 if (GNUNET_OK != 155 TALER_exchange_offline_signkey_validity_verify ( 156 &es->exchange_pub, 157 es->ep_start, 158 es->ep_expire, 159 es->ep_end, 160 &TAH_master_public_key, 161 &es->master_sig)) 162 { 163 TALER_LOG_WARNING ("Invalid signature on exchange signing key\n"); 164 return TALER_MHD_reply_with_error (connection, 165 MHD_HTTP_FORBIDDEN, 166 TALER_EC_AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID, 167 "master signature invalid"); 168 } 169 170 /* execute transaction */ 171 qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls, 172 es); 173 if (0 > qs) 174 { 175 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 176 TALER_LOG_WARNING ("Failed to store exchange signing key in database\n"); 177 return TALER_MHD_reply_with_error (connection, 178 MHD_HTTP_INTERNAL_SERVER_ERROR, 179 TALER_EC_GENERIC_DB_STORE_FAILED, 180 "exchange signing key"); 181 } 182 cached = "verified"; 183 } 184 185 if (0 == strcmp (cached, 186 "verified")) 187 { 188 struct TALER_MasterSignatureP master_sig; 189 190 /* check for revocation */ 191 qs = TAH_eplugin->lookup_signkey_revocation (TAH_eplugin->cls, 192 &es->exchange_pub, 193 &master_sig); 194 if (0 > qs) 195 { 196 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 197 TALER_LOG_WARNING ( 198 "Failed to check for signing key revocation in database\n"); 199 return TALER_MHD_reply_with_error (connection, 200 MHD_HTTP_INTERNAL_SERVER_ERROR, 201 TALER_EC_GENERIC_DB_FETCH_FAILED, 202 "exchange signing key revocation"); 203 } 204 if (0 < qs) 205 cached = "revoked"; 206 } 207 208 /* Cache it, due to concurreny it might already be in the cache, 209 so we do not cache it twice but also don't insist on the 'put' to 210 succeed. */ 211 GNUNET_assert (0 == pthread_mutex_lock (&lock)); 212 (void) GNUNET_CONTAINER_multihashmap_put (cache, 213 &h, 214 (void *) cached, 215 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); 216 GNUNET_assert (0 == pthread_mutex_unlock (&lock)); 217 218 if (0 == strcmp (cached, 219 "revoked")) 220 { 221 TALER_LOG_WARNING ( 222 "Invalid signature on /deposit-confirmation request: key was revoked\n"); 223 return TALER_MHD_reply_with_error (connection, 224 MHD_HTTP_GONE, 225 TALER_EC_AUDITOR_EXCHANGE_SIGNING_KEY_REVOKED, 226 "exchange signing key was revoked"); 227 } 228 229 /* check deposit confirmation signature */ 230 if (GNUNET_OK != 231 TALER_exchange_online_deposit_confirmation_verify ( 232 &dc->h_contract_terms, 233 &dc->h_wire, 234 &dc->h_policy, 235 dc->exchange_timestamp, 236 dc->wire_deadline, 237 dc->refund_deadline, 238 &dc->total_without_fee, 239 dc->num_coins, 240 coin_sigps, 241 &dc->merchant, 242 &dc->exchange_pub, 243 &dc->exchange_sig)) 244 { 245 TALER_LOG_WARNING ( 246 "Invalid signature on /deposit-confirmation request\n"); 247 return TALER_MHD_reply_with_error (connection, 248 MHD_HTTP_FORBIDDEN, 249 TALER_EC_AUDITOR_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID, 250 "exchange signature invalid"); 251 } 252 253 /* execute transaction */ 254 qs = TAH_plugin->insert_deposit_confirmation (TAH_plugin->cls, 255 dc); 256 if (0 > qs) 257 { 258 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 259 TALER_LOG_WARNING ("Failed to store /deposit-confirmation in database\n"); 260 return TALER_MHD_reply_with_error (connection, 261 MHD_HTTP_INTERNAL_SERVER_ERROR, 262 TALER_EC_GENERIC_DB_STORE_FAILED, 263 "deposit confirmation"); 264 } 265 return TALER_MHD_REPLY_JSON_PACK (connection, 266 MHD_HTTP_OK, 267 GNUNET_JSON_pack_string ("status", 268 "DEPOSIT_CONFIRMATION_OK")); 269 } 270 271 272 MHD_RESULT 273 TAH_DEPOSIT_CONFIRMATION_handler ( 274 struct TAH_RequestHandler *rh, 275 struct MHD_Connection *connection, 276 void **connection_cls, 277 const char *upload_data, 278 size_t *upload_data_size, 279 const char *const args[]) 280 { 281 struct TALER_AUDITORDB_DepositConfirmation dc = { 282 .refund_deadline = GNUNET_TIME_UNIT_ZERO_TS 283 }; 284 struct TALER_AUDITORDB_ExchangeSigningKey es; 285 const json_t *jcoin_sigs; 286 const json_t *jcoin_pubs; 287 struct GNUNET_JSON_Specification spec[] = { 288 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 289 &dc.h_contract_terms), 290 GNUNET_JSON_spec_fixed_auto ("h_policy", 291 &dc.h_policy), 292 GNUNET_JSON_spec_fixed_auto ("h_wire", 293 &dc.h_wire), 294 GNUNET_JSON_spec_timestamp ("exchange_timestamp", 295 &dc.exchange_timestamp), 296 GNUNET_JSON_spec_mark_optional ( 297 GNUNET_JSON_spec_timestamp ("refund_deadline", 298 &dc.refund_deadline), 299 NULL), 300 GNUNET_JSON_spec_timestamp ("wire_deadline", 301 &dc.wire_deadline), 302 TALER_JSON_spec_amount ("total_without_fee", 303 TAH_currency, 304 &dc.total_without_fee), 305 GNUNET_JSON_spec_array_const ("coin_pubs", 306 &jcoin_pubs), 307 GNUNET_JSON_spec_array_const ("coin_sigs", 308 &jcoin_sigs), 309 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 310 &dc.merchant), 311 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 312 &dc.exchange_sig), 313 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 314 &dc.exchange_pub), 315 GNUNET_JSON_spec_timestamp ("ep_start", 316 &es.ep_start), 317 GNUNET_JSON_spec_timestamp ("ep_expire", 318 &es.ep_expire), 319 GNUNET_JSON_spec_timestamp ("ep_end", 320 &es.ep_end), 321 GNUNET_JSON_spec_fixed_auto ("master_sig", 322 &es.master_sig), 323 GNUNET_JSON_spec_end () 324 }; 325 unsigned int num_coins; 326 json_t *json; 327 328 (void) rh; 329 (void) connection_cls; 330 (void) upload_data; 331 (void) upload_data_size; 332 { 333 enum GNUNET_GenericReturnValue res; 334 335 res = TALER_MHD_parse_post_json (connection, 336 connection_cls, 337 upload_data, 338 upload_data_size, 339 &json); 340 if (GNUNET_SYSERR == res) 341 return MHD_NO; 342 if ((GNUNET_NO == res) || 343 (NULL == json)) 344 return MHD_YES; 345 res = TALER_MHD_parse_json_data (connection, 346 json, 347 spec); 348 if (GNUNET_SYSERR == res) 349 { 350 json_decref (json); 351 return MHD_NO; /* hard failure */ 352 } 353 if (GNUNET_NO == res) 354 { 355 json_decref (json); 356 return MHD_YES; /* failure */ 357 } 358 dc.master_sig = es.master_sig; 359 } 360 num_coins = json_array_size (jcoin_sigs); 361 if (num_coins != json_array_size (jcoin_pubs)) 362 { 363 GNUNET_break_op (0); 364 json_decref (json); 365 return TALER_MHD_reply_with_ec ( 366 connection, 367 TALER_EC_GENERIC_PARAMETER_MALFORMED, 368 "coin_pubs.length != coin_sigs.length"); 369 } 370 if (0 == num_coins) 371 { 372 GNUNET_break_op (0); 373 json_decref (json); 374 return TALER_MHD_reply_with_ec ( 375 connection, 376 TALER_EC_GENERIC_PARAMETER_MALFORMED, 377 "coin_pubs array is empty"); 378 } 379 { 380 struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins]; 381 struct TALER_CoinSpendSignatureP coin_sigs[num_coins]; 382 MHD_RESULT res; 383 384 for (unsigned int i = 0; i < num_coins; i++) 385 { 386 json_t *jpub = json_array_get (jcoin_pubs, 387 i); 388 json_t *jsig = json_array_get (jcoin_sigs, 389 i); 390 const char *ps = json_string_value (jpub); 391 const char *ss = json_string_value (jsig); 392 393 if ((NULL == ps) || 394 (GNUNET_OK != 395 GNUNET_STRINGS_string_to_data (ps, 396 strlen (ps), 397 &coin_pubs[i], 398 sizeof(coin_pubs[i])))) 399 { 400 GNUNET_break_op (0); 401 json_decref (json); 402 return TALER_MHD_reply_with_ec ( 403 connection, 404 TALER_EC_GENERIC_PARAMETER_MALFORMED, 405 "coin_pub[] malformed"); 406 } 407 if ((NULL == ss) || 408 (GNUNET_OK != 409 GNUNET_STRINGS_string_to_data (ss, 410 strlen (ss), 411 &coin_sigs[i], 412 sizeof(coin_sigs[i])))) 413 { 414 GNUNET_break_op (0); 415 json_decref (json); 416 return TALER_MHD_reply_with_ec ( 417 connection, 418 TALER_EC_GENERIC_PARAMETER_MALFORMED, 419 "coin_sig[] malformed"); 420 } 421 } 422 dc.num_coins = num_coins; 423 dc.coin_pubs = coin_pubs; 424 dc.coin_sigs = coin_sigs; 425 es.exchange_pub = dc.exchange_pub; /* used twice! */ 426 res = verify_and_execute_deposit_confirmation (connection, 427 &dc, 428 &es); 429 GNUNET_JSON_parse_free (spec); 430 json_decref (json); 431 return res; 432 } 433 } 434 435 436 void 437 TEAH_DEPOSIT_CONFIRMATION_init (void) 438 { 439 cache = GNUNET_CONTAINER_multihashmap_create (32, 440 GNUNET_NO); 441 GNUNET_assert (0 == pthread_mutex_init (&lock, NULL)); 442 } 443 444 445 void 446 TEAH_DEPOSIT_CONFIRMATION_done (void) 447 { 448 if (NULL != cache) 449 { 450 GNUNET_CONTAINER_multihashmap_destroy (cache); 451 cache = NULL; 452 GNUNET_assert (0 == pthread_mutex_destroy (&lock)); 453 } 454 }