taler-exchange-httpd_management_post_keys.c (17265B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-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-exchange-httpd_management_post_keys.c 18 * @brief Handle request to POST /management/keys 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_json_lib.h> 24 #include <jansson.h> 25 #include <microhttpd.h> 26 #include <pthread.h> 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_mhd_lib.h" 29 #include "taler/taler_signatures.h" 30 #include "taler-exchange-httpd_keys.h" 31 #include "taler-exchange-httpd_management.h" 32 #include "taler-exchange-httpd_responses.h" 33 34 35 /** 36 * Denomination signature provided. 37 */ 38 struct DenomSig 39 { 40 /** 41 * Hash of a denomination public key. 42 */ 43 struct TALER_DenominationHashP h_denom_pub; 44 45 /** 46 * Master signature for the @e h_denom_pub. 47 */ 48 struct TALER_MasterSignatureP master_sig; 49 50 /** 51 * Fee structure for this key, as per our configuration. 52 */ 53 struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; 54 55 /** 56 * The full public key. 57 */ 58 struct TALER_DenominationPublicKey denom_pub; 59 60 }; 61 62 63 /** 64 * Signkey signature provided. 65 */ 66 struct SigningSig 67 { 68 /** 69 * Online signing key of the exchange. 70 */ 71 struct TALER_ExchangePublicKeyP exchange_pub; 72 73 /** 74 * Master signature for the @e exchange_pub. 75 */ 76 struct TALER_MasterSignatureP master_sig; 77 78 /** 79 * Our meta data on this key. 80 */ 81 struct TALER_EXCHANGEDB_SignkeyMetaData meta; 82 83 }; 84 85 86 /** 87 * Closure for the #add_keys transaction. 88 */ 89 struct AddKeysContext 90 { 91 92 /** 93 * Array of @e nd_sigs denomination signatures. 94 */ 95 struct DenomSig *d_sigs; 96 97 /** 98 * Array of @e ns_sigs signkey signatures. 99 */ 100 struct SigningSig *s_sigs; 101 102 /** 103 * Our key state. 104 */ 105 struct TEH_KeyStateHandle *ksh; 106 107 /** 108 * Length of the d_sigs array. 109 */ 110 unsigned int nd_sigs; 111 112 /** 113 * Length of the n_sigs array. 114 */ 115 unsigned int ns_sigs; 116 117 }; 118 119 120 /** 121 * Compare meta-data of two denomination keys for equality, 122 * except for the "serial" number. 123 * 124 * @param m1 meta data to compare to @a m2 125 * @param m2 meta data to compare to @a m1 126 * @return true if both are equal 127 */ 128 static bool 129 denomination_meta_cmp ( 130 const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m1, 131 const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m2) 132 { 133 if ( (GNUNET_TIME_timestamp_cmp (m1->start, 134 !=, 135 m2->start)) || 136 (GNUNET_TIME_timestamp_cmp (m1->expire_withdraw, 137 !=, 138 m2->expire_withdraw)) || 139 (GNUNET_TIME_timestamp_cmp (m1->expire_deposit, 140 !=, 141 m2->expire_deposit)) || 142 (GNUNET_TIME_timestamp_cmp (m1->expire_legal, 143 !=, 144 m2->expire_legal)) ) 145 return false; 146 if (0 != 147 TALER_amount_cmp (&m1->value, 148 &m2->value)) 149 return false; 150 if (0 != 151 GNUNET_memcmp (&m1->fees, 152 &m2->fees)) 153 return false; 154 if (m1->age_mask.bits != 155 m2->age_mask.bits) 156 return false; 157 return true; 158 } 159 160 161 /** 162 * Compare meta-data of two signing keys for equality. 163 * 164 * @param m1 meta data to compare to @a m2 165 * @param m2 meta data to compare to @a m1 166 * @return true if both are equal 167 */ 168 static bool 169 signkey_meta_cmp ( 170 const struct TALER_EXCHANGEDB_SignkeyMetaData *m1, 171 const struct TALER_EXCHANGEDB_SignkeyMetaData *m2) 172 { 173 if ( (GNUNET_TIME_timestamp_cmp (m1->start, 174 !=, 175 m2->start)) || 176 (GNUNET_TIME_timestamp_cmp (m1->expire_sign, 177 !=, 178 m2->expire_sign)) || 179 (GNUNET_TIME_timestamp_cmp (m1->expire_legal, 180 !=, 181 m2->expire_legal)) ) 182 return false; 183 return true; 184 } 185 186 187 /** 188 * Function implementing database transaction to add offline signing keys. 189 * Runs the transaction logic; IF it returns a non-error code, the transaction 190 * logic MUST NOT queue a MHD response. IF it returns an hard error, the 191 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it 192 * returns the soft error code, the function MAY be called again to retry and 193 * MUST not queue a MHD response. 194 * 195 * @param cls closure with a `struct AddKeysContext` 196 * @param connection MHD request which triggered the transaction 197 * @param[out] mhd_ret set to MHD response status for @a connection, 198 * if transaction failed (!) 199 * @return transaction status 200 */ 201 static enum GNUNET_DB_QueryStatus 202 add_keys (void *cls, 203 struct MHD_Connection *connection, 204 MHD_RESULT *mhd_ret) 205 { 206 struct AddKeysContext *akc = cls; 207 208 /* activate all denomination keys */ 209 for (unsigned int i = 0; i<akc->nd_sigs; i++) 210 { 211 struct DenomSig *d = &akc->d_sigs[i]; 212 enum GNUNET_DB_QueryStatus qs; 213 struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; 214 215 /* For idempotency, check if the key is already active */ 216 qs = TEH_plugin->lookup_denomination_key ( 217 TEH_plugin->cls, 218 &d->h_denom_pub, 219 &meta); 220 if (qs < 0) 221 { 222 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 223 return qs; 224 GNUNET_break (0); 225 *mhd_ret = TALER_MHD_reply_with_error (connection, 226 MHD_HTTP_INTERNAL_SERVER_ERROR, 227 TALER_EC_GENERIC_DB_FETCH_FAILED, 228 "lookup denomination key"); 229 return qs; 230 } 231 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 232 { 233 if (! denomination_meta_cmp (&d->meta, 234 &meta)) 235 { 236 GNUNET_break_op (0); 237 *mhd_ret = TALER_MHD_reply_with_error ( 238 connection, 239 MHD_HTTP_CONFLICT, 240 TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_DENOMINATION_META_DATA, 241 "conflicting meta data previously set for the same denomination key"); 242 return GNUNET_DB_STATUS_HARD_ERROR; 243 } 244 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 245 "Denomination key %s already active, skipping\n", 246 GNUNET_h2s (&d->h_denom_pub.hash)); 247 continue; /* skip, already known */ 248 } 249 250 qs = TEH_plugin->add_denomination_key ( 251 TEH_plugin->cls, 252 &d->h_denom_pub, 253 &d->denom_pub, 254 &d->meta, 255 &d->master_sig); 256 if (qs < 0) 257 { 258 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 259 return qs; 260 GNUNET_break (0); 261 *mhd_ret = TALER_MHD_reply_with_error (connection, 262 MHD_HTTP_INTERNAL_SERVER_ERROR, 263 TALER_EC_GENERIC_DB_STORE_FAILED, 264 "activate denomination key"); 265 return qs; 266 } 267 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 268 "Added offline signature for denomination `%s'\n", 269 GNUNET_h2s (&d->h_denom_pub.hash)); 270 GNUNET_assert (0 != qs); 271 } 272 273 for (unsigned int i = 0; i<akc->ns_sigs; i++) 274 { 275 struct SigningSig *s = &akc->s_sigs[i]; 276 enum GNUNET_DB_QueryStatus qs; 277 struct TALER_EXCHANGEDB_SignkeyMetaData meta; 278 279 qs = TEH_plugin->lookup_signing_key ( 280 TEH_plugin->cls, 281 &s->exchange_pub, 282 &meta); 283 if (qs < 0) 284 { 285 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 286 return qs; 287 GNUNET_break (0); 288 *mhd_ret = TALER_MHD_reply_with_error (connection, 289 MHD_HTTP_INTERNAL_SERVER_ERROR, 290 TALER_EC_GENERIC_DB_FETCH_FAILED, 291 "lookup signing key"); 292 return qs; 293 } 294 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 295 { 296 if (! signkey_meta_cmp (&s->meta, 297 &meta)) 298 { 299 GNUNET_break_op (0); 300 *mhd_ret = TALER_MHD_reply_with_error ( 301 connection, 302 MHD_HTTP_CONFLICT, 303 TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_SIGNKEY_META_DATA, 304 "conflicting meta data previously set for the same signing key"); 305 return GNUNET_DB_STATUS_HARD_ERROR; 306 } 307 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 308 "Signing key %s already active, skipping\n", 309 TALER_B2S (&s->exchange_pub)); 310 continue; /* skip, already known */ 311 } 312 qs = TEH_plugin->activate_signing_key ( 313 TEH_plugin->cls, 314 &s->exchange_pub, 315 &s->meta, 316 &s->master_sig); 317 if (qs < 0) 318 { 319 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 320 return qs; 321 GNUNET_break (0); 322 *mhd_ret = TALER_MHD_reply_with_error (connection, 323 MHD_HTTP_INTERNAL_SERVER_ERROR, 324 TALER_EC_GENERIC_DB_STORE_FAILED, 325 "activate signing key"); 326 return qs; 327 } 328 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 329 "Added offline signature for signing key `%s'\n", 330 TALER_B2S (&s->exchange_pub)); 331 GNUNET_assert (0 != qs); 332 } 333 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */ 334 } 335 336 337 /** 338 * Clean up state in @a akc, but do not free @a akc itself 339 * 340 * @param[in,out] akc state to clean up 341 */ 342 static void 343 cleanup_akc (struct AddKeysContext *akc) 344 { 345 for (unsigned int i = 0; i<akc->nd_sigs; i++) 346 { 347 struct DenomSig *d = &akc->d_sigs[i]; 348 349 TALER_denom_pub_free (&d->denom_pub); 350 } 351 GNUNET_free (akc->d_sigs); 352 GNUNET_free (akc->s_sigs); 353 } 354 355 356 MHD_RESULT 357 TEH_handler_management_post_keys ( 358 struct MHD_Connection *connection, 359 const json_t *root) 360 { 361 struct AddKeysContext akc = { 0 }; 362 const json_t *denom_sigs; 363 const json_t *signkey_sigs; 364 struct GNUNET_JSON_Specification spec[] = { 365 GNUNET_JSON_spec_array_const ("denom_sigs", 366 &denom_sigs), 367 GNUNET_JSON_spec_array_const ("signkey_sigs", 368 &signkey_sigs), 369 GNUNET_JSON_spec_end () 370 }; 371 MHD_RESULT ret; 372 373 { 374 enum GNUNET_GenericReturnValue res; 375 376 res = TALER_MHD_parse_json_data (connection, 377 root, 378 spec); 379 if (GNUNET_SYSERR == res) 380 return MHD_NO; /* hard failure */ 381 if (GNUNET_NO == res) 382 return MHD_YES; /* failure */ 383 } 384 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 385 "Received POST /management/keys request\n"); 386 387 akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */ 388 if (NULL == akc.ksh) 389 { 390 GNUNET_break_op (0); 391 return TALER_MHD_reply_with_error ( 392 connection, 393 MHD_HTTP_INTERNAL_SERVER_ERROR, 394 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 395 "no key state (not even for management)"); 396 } 397 398 akc.nd_sigs = json_array_size (denom_sigs); 399 akc.d_sigs = GNUNET_new_array (akc.nd_sigs, 400 struct DenomSig); 401 for (unsigned int i = 0; i<akc.nd_sigs; i++) 402 { 403 struct DenomSig *d = &akc.d_sigs[i]; 404 struct GNUNET_JSON_Specification ispec[] = { 405 GNUNET_JSON_spec_fixed_auto ("master_sig", 406 &d->master_sig), 407 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 408 &d->h_denom_pub), 409 GNUNET_JSON_spec_end () 410 }; 411 enum GNUNET_GenericReturnValue res; 412 413 res = TALER_MHD_parse_json_data (connection, 414 json_array_get (denom_sigs, 415 i), 416 ispec); 417 if (GNUNET_OK != res) 418 { 419 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 420 "Failure to handle /management/keys\n"); 421 cleanup_akc (&akc); 422 return (GNUNET_NO == res) ? MHD_YES : MHD_NO; 423 } 424 425 res = TEH_keys_load_fees (akc.ksh, 426 &d->h_denom_pub, 427 &d->denom_pub, 428 &d->meta); 429 switch (res) 430 { 431 case GNUNET_SYSERR: 432 ret = TALER_MHD_reply_with_error ( 433 connection, 434 MHD_HTTP_INTERNAL_SERVER_ERROR, 435 TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, 436 GNUNET_h2s (&d->h_denom_pub.hash)); 437 cleanup_akc (&akc); 438 return ret; 439 case GNUNET_NO: 440 ret = TALER_MHD_reply_with_error ( 441 connection, 442 MHD_HTTP_NOT_FOUND, 443 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, 444 GNUNET_h2s (&d->h_denom_pub.hash)); 445 cleanup_akc (&akc); 446 return ret; 447 case GNUNET_OK: 448 break; 449 } 450 /* check signature is valid */ 451 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 452 if (GNUNET_OK != 453 TALER_exchange_offline_denom_validity_verify ( 454 &d->h_denom_pub, 455 d->meta.start, 456 d->meta.expire_withdraw, 457 d->meta.expire_deposit, 458 d->meta.expire_legal, 459 &d->meta.value, 460 &d->meta.fees, 461 &TEH_master_public_key, 462 &d->master_sig)) 463 { 464 GNUNET_break_op (0); 465 ret = TALER_MHD_reply_with_error ( 466 connection, 467 MHD_HTTP_FORBIDDEN, 468 TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID, 469 GNUNET_h2s (&d->h_denom_pub.hash)); 470 cleanup_akc (&akc); 471 return ret; 472 } 473 } 474 475 akc.ns_sigs = json_array_size (signkey_sigs); 476 akc.s_sigs = GNUNET_new_array (akc.ns_sigs, 477 struct SigningSig); 478 for (unsigned int i = 0; i<akc.ns_sigs; i++) 479 { 480 struct SigningSig *s = &akc.s_sigs[i]; 481 struct GNUNET_JSON_Specification ispec[] = { 482 GNUNET_JSON_spec_fixed_auto ("master_sig", 483 &s->master_sig), 484 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 485 &s->exchange_pub), 486 GNUNET_JSON_spec_end () 487 }; 488 enum GNUNET_GenericReturnValue res; 489 490 res = TALER_MHD_parse_json_data (connection, 491 json_array_get (signkey_sigs, 492 i), 493 ispec); 494 if (GNUNET_OK != res) 495 { 496 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 497 "Failure to handle /management/keys\n"); 498 cleanup_akc (&akc); 499 return (GNUNET_NO == res) ? MHD_YES : MHD_NO; 500 } 501 res = TEH_keys_get_timing (&s->exchange_pub, 502 &s->meta); 503 switch (res) 504 { 505 case GNUNET_SYSERR: 506 ret = TALER_MHD_reply_with_error ( 507 connection, 508 MHD_HTTP_INTERNAL_SERVER_ERROR, 509 TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, 510 TALER_B2S (&s->exchange_pub)); 511 cleanup_akc (&akc); 512 return ret; 513 case GNUNET_NO: 514 /* For idempotency, check if the key is already active */ 515 ret = TALER_MHD_reply_with_error ( 516 connection, 517 MHD_HTTP_NOT_FOUND, 518 TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN, 519 TALER_B2S (&s->exchange_pub)); 520 cleanup_akc (&akc); 521 return ret; 522 case GNUNET_OK: 523 break; 524 } 525 526 /* check signature is valid */ 527 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 528 if (GNUNET_OK != 529 TALER_exchange_offline_signkey_validity_verify ( 530 &s->exchange_pub, 531 s->meta.start, 532 s->meta.expire_sign, 533 s->meta.expire_legal, 534 &TEH_master_public_key, 535 &s->master_sig)) 536 { 537 GNUNET_break_op (0); 538 ret = TALER_MHD_reply_with_error ( 539 connection, 540 MHD_HTTP_FORBIDDEN, 541 TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID, 542 TALER_B2S (&s->exchange_pub)); 543 cleanup_akc (&akc); 544 return ret; 545 } 546 } 547 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 548 "Received %u denomination and %u signing key signatures\n", 549 akc.nd_sigs, 550 akc.ns_sigs); 551 { 552 enum GNUNET_GenericReturnValue res; 553 554 res = TEH_DB_run_transaction (connection, 555 "add keys", 556 TEH_MT_REQUEST_OTHER, 557 &ret, 558 &add_keys, 559 &akc); 560 cleanup_akc (&akc); 561 if (GNUNET_SYSERR == res) 562 return ret; 563 } 564 TEH_keys_update_states (); 565 return TALER_MHD_reply_static ( 566 connection, 567 MHD_HTTP_NO_CONTENT, 568 NULL, 569 NULL, 570 0); 571 } 572 573 574 /* end of taler-exchange-httpd_management_management_post_keys.c */