Line data Source code
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 0 : denomination_meta_cmp (
130 : const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m1,
131 : const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m2)
132 : {
133 0 : if ( (GNUNET_TIME_timestamp_cmp (m1->start,
134 : !=,
135 0 : m2->start)) ||
136 0 : (GNUNET_TIME_timestamp_cmp (m1->expire_withdraw,
137 : !=,
138 0 : m2->expire_withdraw)) ||
139 0 : (GNUNET_TIME_timestamp_cmp (m1->expire_deposit,
140 : !=,
141 0 : m2->expire_deposit)) ||
142 0 : (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
143 : !=,
144 : m2->expire_legal)) )
145 0 : return false;
146 0 : if (0 !=
147 0 : TALER_amount_cmp (&m1->value,
148 : &m2->value))
149 0 : return false;
150 0 : if (0 !=
151 0 : GNUNET_memcmp (&m1->fees,
152 : &m2->fees))
153 0 : return false;
154 0 : if (m1->age_mask.bits !=
155 0 : m2->age_mask.bits)
156 0 : return false;
157 0 : 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 0 : signkey_meta_cmp (
170 : const struct TALER_EXCHANGEDB_SignkeyMetaData *m1,
171 : const struct TALER_EXCHANGEDB_SignkeyMetaData *m2)
172 : {
173 0 : if ( (GNUNET_TIME_timestamp_cmp (m1->start,
174 : !=,
175 0 : m2->start)) ||
176 0 : (GNUNET_TIME_timestamp_cmp (m1->expire_sign,
177 : !=,
178 0 : m2->expire_sign)) ||
179 0 : (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
180 : !=,
181 : m2->expire_legal)) )
182 0 : return false;
183 0 : 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 21 : add_keys (void *cls,
203 : struct MHD_Connection *connection,
204 : MHD_RESULT *mhd_ret)
205 : {
206 21 : struct AddKeysContext *akc = cls;
207 :
208 : /* activate all denomination keys */
209 6241 : for (unsigned int i = 0; i<akc->nd_sigs; i++)
210 : {
211 6220 : 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 6220 : qs = TEH_plugin->lookup_denomination_key (
217 6220 : TEH_plugin->cls,
218 6220 : &d->h_denom_pub,
219 : &meta);
220 6220 : if (qs < 0)
221 : {
222 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
223 0 : return qs;
224 0 : GNUNET_break (0);
225 0 : *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 0 : return qs;
230 : }
231 6220 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
232 : {
233 0 : if (! denomination_meta_cmp (&d->meta,
234 : &meta))
235 : {
236 0 : GNUNET_break_op (0);
237 0 : *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 0 : return GNUNET_DB_STATUS_HARD_ERROR;
243 : }
244 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
245 : "Denomination key %s already active, skipping\n",
246 : GNUNET_h2s (&d->h_denom_pub.hash));
247 0 : continue; /* skip, already known */
248 : }
249 :
250 6220 : qs = TEH_plugin->add_denomination_key (
251 6220 : TEH_plugin->cls,
252 6220 : &d->h_denom_pub,
253 6220 : &d->denom_pub,
254 6220 : &d->meta,
255 6220 : &d->master_sig);
256 6220 : if (qs < 0)
257 : {
258 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
259 0 : return qs;
260 0 : GNUNET_break (0);
261 0 : *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 0 : return qs;
266 : }
267 6220 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
268 : "Added offline signature for denomination `%s'\n",
269 : GNUNET_h2s (&d->h_denom_pub.hash));
270 6220 : GNUNET_assert (0 != qs);
271 : }
272 :
273 88 : for (unsigned int i = 0; i<akc->ns_sigs; i++)
274 : {
275 67 : struct SigningSig *s = &akc->s_sigs[i];
276 : enum GNUNET_DB_QueryStatus qs;
277 : struct TALER_EXCHANGEDB_SignkeyMetaData meta;
278 :
279 67 : qs = TEH_plugin->lookup_signing_key (
280 67 : TEH_plugin->cls,
281 67 : &s->exchange_pub,
282 : &meta);
283 67 : if (qs < 0)
284 : {
285 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
286 0 : return qs;
287 0 : GNUNET_break (0);
288 0 : *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 0 : return qs;
293 : }
294 67 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
295 : {
296 0 : if (! signkey_meta_cmp (&s->meta,
297 : &meta))
298 : {
299 0 : GNUNET_break_op (0);
300 0 : *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 0 : return GNUNET_DB_STATUS_HARD_ERROR;
306 : }
307 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
308 : "Signing key %s already active, skipping\n",
309 : TALER_B2S (&s->exchange_pub));
310 0 : continue; /* skip, already known */
311 : }
312 67 : qs = TEH_plugin->activate_signing_key (
313 67 : TEH_plugin->cls,
314 67 : &s->exchange_pub,
315 67 : &s->meta,
316 67 : &s->master_sig);
317 67 : if (qs < 0)
318 : {
319 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
320 0 : return qs;
321 0 : GNUNET_break (0);
322 0 : *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 0 : return qs;
327 : }
328 67 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
329 : "Added offline signature for signing key `%s'\n",
330 : TALER_B2S (&s->exchange_pub));
331 67 : GNUNET_assert (0 != qs);
332 : }
333 21 : 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 21 : cleanup_akc (struct AddKeysContext *akc)
344 : {
345 6241 : for (unsigned int i = 0; i<akc->nd_sigs; i++)
346 : {
347 6220 : struct DenomSig *d = &akc->d_sigs[i];
348 :
349 6220 : TALER_denom_pub_free (&d->denom_pub);
350 : }
351 21 : GNUNET_free (akc->d_sigs);
352 21 : GNUNET_free (akc->s_sigs);
353 21 : }
354 :
355 :
356 : MHD_RESULT
357 21 : TEH_handler_management_post_keys (
358 : struct MHD_Connection *connection,
359 : const json_t *root)
360 : {
361 21 : struct AddKeysContext akc = { 0 };
362 : const json_t *denom_sigs;
363 : const json_t *signkey_sigs;
364 : struct GNUNET_JSON_Specification spec[] = {
365 21 : GNUNET_JSON_spec_array_const ("denom_sigs",
366 : &denom_sigs),
367 21 : GNUNET_JSON_spec_array_const ("signkey_sigs",
368 : &signkey_sigs),
369 21 : GNUNET_JSON_spec_end ()
370 : };
371 : MHD_RESULT ret;
372 :
373 : {
374 : enum GNUNET_GenericReturnValue res;
375 :
376 21 : res = TALER_MHD_parse_json_data (connection,
377 : root,
378 : spec);
379 21 : if (GNUNET_SYSERR == res)
380 0 : return MHD_NO; /* hard failure */
381 21 : if (GNUNET_NO == res)
382 0 : return MHD_YES; /* failure */
383 : }
384 21 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
385 : "Received POST /management/keys request\n");
386 :
387 21 : akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */
388 21 : if (NULL == akc.ksh)
389 : {
390 0 : GNUNET_break_op (0);
391 0 : 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 21 : akc.nd_sigs = json_array_size (denom_sigs);
399 21 : akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
400 : struct DenomSig);
401 6241 : for (unsigned int i = 0; i<akc.nd_sigs; i++)
402 : {
403 6220 : struct DenomSig *d = &akc.d_sigs[i];
404 : struct GNUNET_JSON_Specification ispec[] = {
405 6220 : GNUNET_JSON_spec_fixed_auto ("master_sig",
406 : &d->master_sig),
407 6220 : GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
408 : &d->h_denom_pub),
409 6220 : GNUNET_JSON_spec_end ()
410 : };
411 : enum GNUNET_GenericReturnValue res;
412 :
413 6220 : res = TALER_MHD_parse_json_data (connection,
414 6220 : json_array_get (denom_sigs,
415 : i),
416 : ispec);
417 6220 : if (GNUNET_OK != res)
418 : {
419 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
420 : "Failure to handle /management/keys\n");
421 0 : cleanup_akc (&akc);
422 0 : return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
423 : }
424 :
425 6220 : res = TEH_keys_load_fees (akc.ksh,
426 6220 : &d->h_denom_pub,
427 : &d->denom_pub,
428 : &d->meta);
429 6220 : switch (res)
430 : {
431 0 : case GNUNET_SYSERR:
432 0 : ret = TALER_MHD_reply_with_error (
433 : connection,
434 : MHD_HTTP_INTERNAL_SERVER_ERROR,
435 : TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
436 0 : GNUNET_h2s (&d->h_denom_pub.hash));
437 0 : cleanup_akc (&akc);
438 0 : return ret;
439 0 : case GNUNET_NO:
440 0 : ret = TALER_MHD_reply_with_error (
441 : connection,
442 : MHD_HTTP_NOT_FOUND,
443 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
444 0 : GNUNET_h2s (&d->h_denom_pub.hash));
445 0 : cleanup_akc (&akc);
446 0 : return ret;
447 6220 : case GNUNET_OK:
448 6220 : break;
449 : }
450 : /* check signature is valid */
451 6220 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
452 6220 : if (GNUNET_OK !=
453 6220 : TALER_exchange_offline_denom_validity_verify (
454 6220 : &d->h_denom_pub,
455 : d->meta.start,
456 : d->meta.expire_withdraw,
457 : d->meta.expire_deposit,
458 : d->meta.expire_legal,
459 6220 : &d->meta.value,
460 6220 : &d->meta.fees,
461 : &TEH_master_public_key,
462 6220 : &d->master_sig))
463 : {
464 0 : GNUNET_break_op (0);
465 0 : ret = TALER_MHD_reply_with_error (
466 : connection,
467 : MHD_HTTP_FORBIDDEN,
468 : TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
469 0 : GNUNET_h2s (&d->h_denom_pub.hash));
470 0 : cleanup_akc (&akc);
471 0 : return ret;
472 : }
473 : }
474 :
475 21 : akc.ns_sigs = json_array_size (signkey_sigs);
476 21 : akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
477 : struct SigningSig);
478 88 : for (unsigned int i = 0; i<akc.ns_sigs; i++)
479 : {
480 67 : struct SigningSig *s = &akc.s_sigs[i];
481 : struct GNUNET_JSON_Specification ispec[] = {
482 67 : GNUNET_JSON_spec_fixed_auto ("master_sig",
483 : &s->master_sig),
484 67 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
485 : &s->exchange_pub),
486 67 : GNUNET_JSON_spec_end ()
487 : };
488 : enum GNUNET_GenericReturnValue res;
489 :
490 67 : res = TALER_MHD_parse_json_data (connection,
491 67 : json_array_get (signkey_sigs,
492 : i),
493 : ispec);
494 67 : if (GNUNET_OK != res)
495 : {
496 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
497 : "Failure to handle /management/keys\n");
498 0 : cleanup_akc (&akc);
499 0 : return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
500 : }
501 67 : res = TEH_keys_get_timing (&s->exchange_pub,
502 : &s->meta);
503 67 : switch (res)
504 : {
505 0 : case GNUNET_SYSERR:
506 0 : ret = TALER_MHD_reply_with_error (
507 : connection,
508 : MHD_HTTP_INTERNAL_SERVER_ERROR,
509 : TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
510 0 : TALER_B2S (&s->exchange_pub));
511 0 : cleanup_akc (&akc);
512 0 : return ret;
513 0 : case GNUNET_NO:
514 : /* For idempotency, check if the key is already active */
515 0 : ret = TALER_MHD_reply_with_error (
516 : connection,
517 : MHD_HTTP_NOT_FOUND,
518 : TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
519 0 : TALER_B2S (&s->exchange_pub));
520 0 : cleanup_akc (&akc);
521 0 : return ret;
522 67 : case GNUNET_OK:
523 67 : break;
524 : }
525 :
526 : /* check signature is valid */
527 67 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
528 67 : if (GNUNET_OK !=
529 67 : TALER_exchange_offline_signkey_validity_verify (
530 67 : &s->exchange_pub,
531 : s->meta.start,
532 : s->meta.expire_sign,
533 : s->meta.expire_legal,
534 : &TEH_master_public_key,
535 67 : &s->master_sig))
536 : {
537 0 : GNUNET_break_op (0);
538 0 : ret = TALER_MHD_reply_with_error (
539 : connection,
540 : MHD_HTTP_FORBIDDEN,
541 : TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
542 0 : TALER_B2S (&s->exchange_pub));
543 0 : cleanup_akc (&akc);
544 0 : return ret;
545 : }
546 : }
547 21 : 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 21 : res = TEH_DB_run_transaction (connection,
555 : "add keys",
556 : TEH_MT_REQUEST_OTHER,
557 : &ret,
558 : &add_keys,
559 : &akc);
560 21 : cleanup_akc (&akc);
561 21 : if (GNUNET_SYSERR == res)
562 0 : return ret;
563 : }
564 21 : TEH_keys_update_states ();
565 21 : 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 */
|