taler-auditor-httpd_put-deposit-confirmation.c (16193B)
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/lookup_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 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_lookup_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_JSON_PACK (connection, 269 MHD_HTTP_OK, 270 GNUNET_JSON_pack_string ("status", 271 "DEPOSIT_CONFIRMATION_OK")); 272 } 273 274 275 MHD_RESULT 276 TAH_put_deposit_confirmation ( 277 struct TAH_RequestHandler *rh, 278 struct MHD_Connection *connection, 279 void **connection_cls, 280 const char *upload_data, 281 size_t *upload_data_size, 282 const char *const args[]) 283 { 284 struct TALER_AUDITORDB_DepositConfirmation dc = { 285 .refund_deadline = GNUNET_TIME_UNIT_ZERO_TS 286 }; 287 struct TALER_AUDITORDB_ExchangeSigningKey es; 288 const json_t *jcoin_sigs; 289 const json_t *jcoin_pubs; 290 struct GNUNET_JSON_Specification spec[] = { 291 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 292 &dc.h_contract_terms), 293 GNUNET_JSON_spec_fixed_auto ("h_policy", 294 &dc.h_policy), 295 GNUNET_JSON_spec_fixed_auto ("h_wire", 296 &dc.h_wire), 297 GNUNET_JSON_spec_timestamp ("exchange_timestamp", 298 &dc.exchange_timestamp), 299 GNUNET_JSON_spec_mark_optional ( 300 GNUNET_JSON_spec_timestamp ("refund_deadline", 301 &dc.refund_deadline), 302 NULL), 303 GNUNET_JSON_spec_timestamp ("wire_deadline", 304 &dc.wire_deadline), 305 TALER_JSON_spec_amount ("total_without_fee", 306 TAH_currency, 307 &dc.total_without_fee), 308 GNUNET_JSON_spec_array_const ("coin_pubs", 309 &jcoin_pubs), 310 GNUNET_JSON_spec_array_const ("coin_sigs", 311 &jcoin_sigs), 312 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 313 &dc.merchant), 314 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 315 &dc.exchange_sig), 316 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 317 &dc.exchange_pub), 318 GNUNET_JSON_spec_timestamp ("ep_start", 319 &es.ep_start), 320 GNUNET_JSON_spec_timestamp ("ep_expire", 321 &es.ep_expire), 322 GNUNET_JSON_spec_timestamp ("ep_end", 323 &es.ep_end), 324 GNUNET_JSON_spec_fixed_auto ("master_sig", 325 &es.master_sig), 326 GNUNET_JSON_spec_end () 327 }; 328 unsigned int num_coins; 329 json_t *json; 330 331 (void) rh; 332 (void) connection_cls; 333 (void) upload_data; 334 (void) upload_data_size; 335 { 336 enum GNUNET_GenericReturnValue res; 337 338 res = TALER_MHD_parse_post_json (connection, 339 connection_cls, 340 upload_data, 341 upload_data_size, 342 &json); 343 if (GNUNET_SYSERR == res) 344 return MHD_NO; 345 if ((GNUNET_NO == res) || 346 (NULL == json)) 347 return MHD_YES; 348 res = TALER_MHD_parse_json_data (connection, 349 json, 350 spec); 351 if (GNUNET_SYSERR == res) 352 { 353 json_decref (json); 354 return MHD_NO; /* hard failure */ 355 } 356 if (GNUNET_NO == res) 357 { 358 json_decref (json); 359 return MHD_YES; /* failure */ 360 } 361 dc.master_sig = es.master_sig; 362 } 363 num_coins = json_array_size (jcoin_sigs); 364 if (num_coins != json_array_size (jcoin_pubs)) 365 { 366 GNUNET_break_op (0); 367 json_decref (json); 368 return TALER_MHD_reply_with_ec ( 369 connection, 370 TALER_EC_GENERIC_PARAMETER_MALFORMED, 371 "coin_pubs.length != coin_sigs.length"); 372 } 373 if (0 == num_coins) 374 { 375 GNUNET_break_op (0); 376 json_decref (json); 377 return TALER_MHD_reply_with_ec ( 378 connection, 379 TALER_EC_GENERIC_PARAMETER_MALFORMED, 380 "coin_pubs array is empty"); 381 } 382 { 383 struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins]; 384 struct TALER_CoinSpendSignatureP coin_sigs[num_coins]; 385 MHD_RESULT res; 386 387 for (unsigned int i = 0; i < num_coins; i++) 388 { 389 json_t *jpub = json_array_get (jcoin_pubs, 390 i); 391 json_t *jsig = json_array_get (jcoin_sigs, 392 i); 393 const char *ps = json_string_value (jpub); 394 const char *ss = json_string_value (jsig); 395 396 if ((NULL == ps) || 397 (GNUNET_OK != 398 GNUNET_STRINGS_string_to_data (ps, 399 strlen (ps), 400 &coin_pubs[i], 401 sizeof(coin_pubs[i])))) 402 { 403 GNUNET_break_op (0); 404 json_decref (json); 405 return TALER_MHD_reply_with_ec ( 406 connection, 407 TALER_EC_GENERIC_PARAMETER_MALFORMED, 408 "coin_pub[] malformed"); 409 } 410 if ((NULL == ss) || 411 (GNUNET_OK != 412 GNUNET_STRINGS_string_to_data (ss, 413 strlen (ss), 414 &coin_sigs[i], 415 sizeof(coin_sigs[i])))) 416 { 417 GNUNET_break_op (0); 418 json_decref (json); 419 return TALER_MHD_reply_with_ec ( 420 connection, 421 TALER_EC_GENERIC_PARAMETER_MALFORMED, 422 "coin_sig[] malformed"); 423 } 424 } 425 dc.num_coins = num_coins; 426 dc.coin_pubs = coin_pubs; 427 dc.coin_sigs = coin_sigs; 428 es.exchange_pub = dc.exchange_pub; /* used twice! */ 429 res = verify_and_execute_deposit_confirmation (connection, 430 &dc, 431 &es); 432 GNUNET_JSON_parse_free (spec); 433 json_decref (json); 434 return res; 435 } 436 } 437 438 439 void 440 TEAH_put_deposit_confirmation_init (void) 441 { 442 cache = GNUNET_CONTAINER_multihashmap_create (32, 443 GNUNET_NO); 444 GNUNET_assert (0 == pthread_mutex_init (&lock, NULL)); 445 } 446 447 448 void 449 TEAH_put_deposit_confirmation_done (void) 450 { 451 if (NULL != cache) 452 { 453 GNUNET_CONTAINER_multihashmap_destroy (cache); 454 cache = NULL; 455 GNUNET_assert (0 == pthread_mutex_destroy (&lock)); 456 } 457 }