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