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