2 This file is part of GNUnet.
3 Copyright (C) 2013, 2014, 2016 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file revocation/gnunet-service-revocation.c
23 * @brief key revocation service
24 * @author Christian Grothoff
26 * The purpose of this service is to allow users to permanently revoke
27 * (compromised) keys. This is done by flooding the network with the
28 * revocation requests. To reduce the attack potential offered by such
29 * flooding, revocations must include a proof of work. We use the
30 * set service for efficiently computing the union of revocations of
34 * - optimization: avoid sending revocation back to peer that we got it from;
35 * - optimization: have randomized delay in sending revocations to other peers
36 * to make it rare to traverse each link twice (NSE-style)
40 #include "gnunet_util_lib.h"
41 #include "gnunet_block_lib.h"
42 #include "gnunet_constants.h"
43 #include "gnunet_protocols.h"
44 #include "gnunet_signatures.h"
45 #include "gnunet_statistics_service.h"
46 #include "gnunet_core_service.h"
47 #include "gnunet_revocation_service.h"
48 #include "gnunet_set_service.h"
49 #include "revocation.h"
54 * Per-peer information.
58 * Queue for sending messages to this peer.
60 struct GNUNET_MQ_Handle *mq;
63 * What is the identity of the peer?
65 struct GNUNET_PeerIdentity id;
68 * Tasked used to trigger the set union operation.
70 struct GNUNET_SCHEDULER_Task *transmit_task;
73 * Handle to active set union operation (over revocation sets).
75 struct GNUNET_SET_OperationHandle *so;
80 * Set from all revocations known to us.
82 static struct GNUNET_SET_Handle *revocation_set;
85 * Hash map with all revoked keys, maps the hash of the public key
86 * to the respective `struct RevokeMessage`.
88 static struct GNUNET_CONTAINER_MultiHashMap *revocation_map;
91 * Handle to our current configuration.
93 static const struct GNUNET_CONFIGURATION_Handle *cfg;
96 * Handle to the statistics service.
98 static struct GNUNET_STATISTICS_Handle *stats;
101 * Handle to the core service (for flooding)
103 static struct GNUNET_CORE_Handle *core_api;
106 * Map of all connected peers.
108 static struct GNUNET_CONTAINER_MultiPeerMap *peers;
111 * The peer identity of this peer.
113 static struct GNUNET_PeerIdentity my_identity;
116 * File handle for the revocation database.
118 static struct GNUNET_DISK_FileHandle *revocation_db;
121 * Handle for us listening to incoming revocation set union requests.
123 static struct GNUNET_SET_ListenHandle *revocation_union_listen_handle;
126 * Amount of work required (W-bit collisions) for REVOCATION proofs, in collision-bits.
128 static unsigned long long revocation_work_required;
131 * Our application ID for set union operations. Must be the
132 * same for all (compatible) peers.
134 static struct GNUNET_HashCode revocation_set_union_app_id;
138 * Create a new PeerEntry and add it to the peers multipeermap.
140 * @param peer the peer identity
141 * @return a pointer to the new PeerEntry
143 static struct PeerEntry *
144 new_peer_entry(const struct GNUNET_PeerIdentity *peer)
146 struct PeerEntry *peer_entry;
148 peer_entry = GNUNET_new(struct PeerEntry);
149 peer_entry->id = *peer;
150 GNUNET_assert(GNUNET_OK ==
151 GNUNET_CONTAINER_multipeermap_put(peers,
154 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
160 * An revoke message has been received, check that it is well-formed.
162 * @param rm the message to verify
163 * @return #GNUNET_YES if the message is verified
164 * #GNUNET_NO if the key/signature don't verify
167 verify_revoke_message(const struct RevokeMessage *rm)
170 GNUNET_REVOCATION_check_pow(&rm->public_key,
172 (unsigned int)revocation_work_required))
174 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
175 "Proof of work invalid!\n");
180 GNUNET_CRYPTO_ecdsa_verify(GNUNET_SIGNATURE_PURPOSE_REVOCATION,
193 * Handle client connecting to the service.
196 * @param client the new client
197 * @param mq the message queue of @a client
201 client_connect_cb(void *cls,
202 struct GNUNET_SERVICE_Client *client,
203 struct GNUNET_MQ_Handle *mq)
210 * Handle client connecting to the service.
213 * @param client the new client
214 * @param app_cls must alias @a client
217 client_disconnect_cb(void *cls,
218 struct GNUNET_SERVICE_Client *client,
221 GNUNET_assert(client == app_cls);
226 * Handle QUERY message from client.
228 * @param cls client who sent the message
229 * @param qm the message received
232 handle_query_message(void *cls,
233 const struct QueryMessage *qm)
235 struct GNUNET_SERVICE_Client *client = cls;
236 struct GNUNET_MQ_Envelope *env;
237 struct QueryResponseMessage *qrm;
238 struct GNUNET_HashCode hc;
241 GNUNET_CRYPTO_hash(&qm->key,
242 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
244 res = GNUNET_CONTAINER_multihashmap_contains(revocation_map,
246 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
248 ? "Received revocation check for valid key `%s' from client\n"
249 : "Received revocation check for revoked key `%s' from client\n",
251 env = GNUNET_MQ_msg(qrm,
252 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY_RESPONSE);
253 qrm->is_valid = htonl((GNUNET_YES == res) ? GNUNET_NO : GNUNET_YES);
254 GNUNET_MQ_send(GNUNET_SERVICE_client_get_mq(client),
256 GNUNET_SERVICE_client_continue(client);
261 * Flood the given revocation message to all neighbours.
263 * @param cls the `struct RevokeMessage` to flood
264 * @param target a neighbour
265 * @param value our `struct PeerEntry` for the neighbour
266 * @return #GNUNET_OK (continue to iterate)
270 const struct GNUNET_PeerIdentity *target,
273 const struct RevokeMessage *rm = cls;
274 struct PeerEntry *pe = value;
275 struct GNUNET_MQ_Envelope *e;
276 struct RevokeMessage *cp;
279 return GNUNET_OK; /* peer connected to us via SET,
280 but we have no direct CORE
281 connection for flooding */
282 e = GNUNET_MQ_msg(cp,
283 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE);
285 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
286 "Flooding revocation to `%s'\n",
288 GNUNET_MQ_send(pe->mq,
295 * Publicize revocation message. Stores the message locally in the
296 * database and passes it to all connected neighbours (and adds it to
297 * the set for future connections).
299 * @param rm message to publicize
300 * @return #GNUNET_OK on success, #GNUNET_NO if we encountered an error,
301 * #GNUNET_SYSERR if the message was malformed
304 publicize_rm(const struct RevokeMessage *rm)
306 struct RevokeMessage *cp;
307 struct GNUNET_HashCode hc;
308 struct GNUNET_SET_Element e;
310 GNUNET_CRYPTO_hash(&rm->public_key,
311 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
314 GNUNET_CONTAINER_multihashmap_contains(revocation_map,
317 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
318 "Duplicate revocation received from peer. Ignored.\n");
322 verify_revoke_message(rm))
325 return GNUNET_SYSERR;
328 if (sizeof(struct RevokeMessage) !=
329 GNUNET_DISK_file_write(revocation_db,
331 sizeof(struct RevokeMessage)))
333 GNUNET_log_strerror(GNUNET_ERROR_TYPE_ERROR,
338 GNUNET_DISK_file_sync(revocation_db))
340 GNUNET_log_strerror(GNUNET_ERROR_TYPE_ERROR,
344 /* keep copy in memory */
345 cp = (struct RevokeMessage *)GNUNET_copy_message(&rm->header);
346 GNUNET_break(GNUNET_OK ==
347 GNUNET_CONTAINER_multihashmap_put(revocation_map,
350 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
351 /* add to set for future connections */
352 e.size = htons(rm->header.size);
353 e.element_type = GNUNET_BLOCK_TYPE_REVOCATION;
356 GNUNET_SET_add_element(revocation_set,
366 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
367 "Added revocation info to SET\n");
369 /* flood to neighbours */
370 GNUNET_CONTAINER_multipeermap_iterate(peers,
378 * Handle REVOKE message from client.
380 * @param cls client who sent the message
381 * @param rm the message received
384 handle_revoke_message(void *cls,
385 const struct RevokeMessage *rm)
387 struct GNUNET_SERVICE_Client *client = cls;
388 struct GNUNET_MQ_Envelope *env;
389 struct RevocationResponseMessage *rrm;
392 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
393 "Received REVOKE message from client\n");
394 if (GNUNET_SYSERR == (ret = publicize_rm(rm)))
397 GNUNET_SERVICE_client_drop(client);
400 env = GNUNET_MQ_msg(rrm,
401 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE_RESPONSE);
402 rrm->is_valid = htonl((GNUNET_OK == ret) ? GNUNET_NO : GNUNET_YES);
403 GNUNET_MQ_send(GNUNET_SERVICE_client_get_mq(client),
405 GNUNET_SERVICE_client_continue(client);
410 * Core handler for flooded revocation messages.
412 * @param cls closure unused
413 * @param rm revocation message
416 handle_p2p_revoke(void *cls,
417 const struct RevokeMessage *rm)
419 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
420 "Received REVOKE message\n");
421 GNUNET_break_op(GNUNET_SYSERR !=
427 * Callback for set operation results. Called for each element in the
428 * result set. Each element contains a revocation, which we should
429 * validate and then add to our revocation list (and set).
432 * @param element a result element, only valid if status is #GNUNET_SET_STATUS_OK
433 * @param current_size current set size
434 * @param status see `enum GNUNET_SET_Status`
437 add_revocation(void *cls,
438 const struct GNUNET_SET_Element *element,
439 uint64_t current_size,
440 enum GNUNET_SET_Status status)
442 struct PeerEntry *peer_entry = cls;
443 const struct RevokeMessage *rm;
447 case GNUNET_SET_STATUS_OK:
448 if (element->size != sizeof(struct RevokeMessage))
453 if (GNUNET_BLOCK_TYPE_REVOCATION != element->element_type)
455 GNUNET_STATISTICS_update(stats,
456 gettext_noop("# unsupported revocations received via set union"),
462 (void)handle_p2p_revoke(NULL,
464 GNUNET_STATISTICS_update(stats,
465 gettext_noop("# revocation messages received via set union"),
469 case GNUNET_SET_STATUS_FAILURE:
470 GNUNET_log(GNUNET_ERROR_TYPE_WARNING,
471 _("Error computing revocation set union with %s\n"),
472 GNUNET_i2s(&peer_entry->id));
473 peer_entry->so = NULL;
474 GNUNET_STATISTICS_update(stats,
475 gettext_noop("# revocation set unions failed"),
480 case GNUNET_SET_STATUS_HALF_DONE:
483 case GNUNET_SET_STATUS_DONE:
484 peer_entry->so = NULL;
485 GNUNET_STATISTICS_update(stats,
486 gettext_noop("# revocation set unions completed"),
499 * The timeout for performing the set union has expired,
500 * run the set operation on the revocation certificates.
505 transmit_task_cb(void *cls)
507 struct PeerEntry *peer_entry = cls;
509 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
510 "Starting set exchange with peer `%s'\n",
511 GNUNET_i2s(&peer_entry->id));
512 peer_entry->transmit_task = NULL;
513 GNUNET_assert(NULL == peer_entry->so);
514 peer_entry->so = GNUNET_SET_prepare(&peer_entry->id,
515 &revocation_set_union_app_id,
517 GNUNET_SET_RESULT_ADDED,
518 (struct GNUNET_SET_Option[]) { { 0 } },
522 GNUNET_SET_commit(peer_entry->so,
525 GNUNET_log(GNUNET_ERROR_TYPE_WARNING,
526 _("SET service crashed, terminating revocation service\n"));
527 GNUNET_SCHEDULER_shutdown();
534 * Method called whenever a peer connects. Sets up the PeerEntry and
535 * schedules the initial revocation set exchange with this peer.
538 * @param peer peer identity this notification is about
541 handle_core_connect(void *cls,
542 const struct GNUNET_PeerIdentity *peer,
543 struct GNUNET_MQ_Handle *mq)
545 struct PeerEntry *peer_entry;
546 struct GNUNET_HashCode my_hash;
547 struct GNUNET_HashCode peer_hash;
549 if (0 == GNUNET_memcmp(peer,
555 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
556 "Peer `%s' connected to us\n",
558 GNUNET_STATISTICS_update(stats,
562 peer_entry = GNUNET_CONTAINER_multipeermap_get(peers,
564 if (NULL != peer_entry)
566 /* This can happen if "core"'s notification is a tad late
567 and CADET+SET were faster and already produced a
568 #handle_revocation_union_request() for us to deal
569 with. This should be rare, but isn't impossible. */
573 peer_entry = new_peer_entry(peer);
575 GNUNET_CRYPTO_hash(&my_identity,
578 GNUNET_CRYPTO_hash(peer,
581 if (0 < GNUNET_CRYPTO_hash_cmp(&my_hash,
584 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
585 "Starting SET operation with peer `%s'\n",
587 peer_entry->transmit_task =
588 GNUNET_SCHEDULER_add_delayed(GNUNET_TIME_UNIT_SECONDS,
597 * Method called whenever a peer disconnects. Deletes the PeerEntry and cancels
598 * any pending transmission requests to that peer.
601 * @param peer peer identity this notification is about
602 * @param internal_cls our `struct PeerEntry` for this peer
605 handle_core_disconnect(void *cls,
606 const struct GNUNET_PeerIdentity *peer,
609 struct PeerEntry *peer_entry = internal_cls;
611 if (0 == GNUNET_memcmp(peer,
614 GNUNET_assert(NULL != peer_entry);
615 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
616 "Peer `%s' disconnected from us\n",
618 GNUNET_assert(GNUNET_YES ==
619 GNUNET_CONTAINER_multipeermap_remove(peers,
622 if (NULL != peer_entry->transmit_task)
624 GNUNET_SCHEDULER_cancel(peer_entry->transmit_task);
625 peer_entry->transmit_task = NULL;
627 if (NULL != peer_entry->so)
629 GNUNET_SET_operation_cancel(peer_entry->so);
630 peer_entry->so = NULL;
632 GNUNET_free(peer_entry);
633 GNUNET_STATISTICS_update(stats,
641 * Free all values in a hash map.
645 * @param value value to free
646 * @return #GNUNET_OK (continue to iterate)
649 free_entry(void *cls,
650 const struct GNUNET_HashCode *key,
659 * Task run during shutdown.
664 shutdown_task(void *cls)
666 if (NULL != revocation_set)
668 GNUNET_SET_destroy(revocation_set);
669 revocation_set = NULL;
671 if (NULL != revocation_union_listen_handle)
673 GNUNET_SET_listen_cancel(revocation_union_listen_handle);
674 revocation_union_listen_handle = NULL;
676 if (NULL != core_api)
678 GNUNET_CORE_disconnect(core_api);
683 GNUNET_STATISTICS_destroy(stats, GNUNET_NO);
688 GNUNET_CONTAINER_multipeermap_destroy(peers);
691 if (NULL != revocation_db)
693 GNUNET_DISK_file_close(revocation_db);
694 revocation_db = NULL;
696 GNUNET_CONTAINER_multihashmap_iterate(revocation_map,
699 GNUNET_CONTAINER_multihashmap_destroy(revocation_map);
704 * Called on core init/fail.
706 * @param cls service closure
707 * @param identity the public identity of this peer
711 const struct GNUNET_PeerIdentity *identity)
713 if (NULL == identity)
715 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
716 "Connection to core FAILED!\n");
717 GNUNET_SCHEDULER_shutdown();
720 my_identity = *identity;
725 * Called when another peer wants to do a set operation with the
726 * local peer. If a listen error occurs, the 'request' is NULL.
729 * @param other_peer the other peer
730 * @param context_msg message with application specific information from
732 * @param request request from the other peer (never NULL), use GNUNET_SET_accept()
733 * to accept it, otherwise the request will be refused
734 * Note that we can't just return value from the listen callback,
735 * as it is also necessary to specify the set we want to do the
736 * operation with, whith sometimes can be derived from the context
737 * message. It's necessary to specify the timeout.
740 handle_revocation_union_request(void *cls,
741 const struct GNUNET_PeerIdentity *other_peer,
742 const struct GNUNET_MessageHeader *context_msg,
743 struct GNUNET_SET_Request *request)
745 struct PeerEntry *peer_entry;
752 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
753 "Received set exchange request from peer `%s'\n",
754 GNUNET_i2s(other_peer));
755 peer_entry = GNUNET_CONTAINER_multipeermap_get(peers,
757 if (NULL == peer_entry)
759 peer_entry = new_peer_entry(other_peer);
761 if (NULL != peer_entry->so)
766 peer_entry->so = GNUNET_SET_accept(request,
767 GNUNET_SET_RESULT_ADDED,
768 (struct GNUNET_SET_Option[]) { { 0 } },
772 GNUNET_SET_commit(peer_entry->so,
776 GNUNET_SCHEDULER_shutdown();
783 * Handle network size estimate clients.
786 * @param server the initialized server
787 * @param c configuration to use
791 const struct GNUNET_CONFIGURATION_Handle *c,
792 struct GNUNET_SERVICE_Handle *service)
794 struct GNUNET_MQ_MessageHandler core_handlers[] = {
795 GNUNET_MQ_hd_fixed_size(p2p_revoke,
796 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
797 struct RevokeMessage,
799 GNUNET_MQ_handler_end()
803 struct RevokeMessage *rm;
804 struct GNUNET_HashCode hc;
806 GNUNET_CRYPTO_hash("revocation-set-union-application-id",
807 strlen("revocation-set-union-application-id"),
808 &revocation_set_union_app_id);
810 GNUNET_CONFIGURATION_get_value_filename(c,
815 GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR,
818 GNUNET_SCHEDULER_shutdown();
822 revocation_map = GNUNET_CONTAINER_multihashmap_create(16,
825 GNUNET_CONFIGURATION_get_value_number(cfg,
828 &revocation_work_required))
830 GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR,
833 GNUNET_SCHEDULER_shutdown();
837 if (revocation_work_required >= sizeof(struct GNUNET_HashCode) * 8)
839 GNUNET_log_config_invalid(GNUNET_ERROR_TYPE_ERROR,
842 _("Value is too large.\n"));
843 GNUNET_SCHEDULER_shutdown();
847 revocation_set = GNUNET_SET_create(cfg,
848 GNUNET_SET_OPERATION_UNION);
849 revocation_union_listen_handle
850 = GNUNET_SET_listen(cfg,
851 GNUNET_SET_OPERATION_UNION,
852 &revocation_set_union_app_id,
853 &handle_revocation_union_request,
855 revocation_db = GNUNET_DISK_file_open(fn,
856 GNUNET_DISK_OPEN_READWRITE |
857 GNUNET_DISK_OPEN_CREATE,
858 GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE |
859 GNUNET_DISK_PERM_GROUP_READ |
860 GNUNET_DISK_PERM_OTHER_READ);
861 if (NULL == revocation_db)
863 GNUNET_log_config_invalid(GNUNET_ERROR_TYPE_ERROR,
866 _("Could not open revocation database file!"));
867 GNUNET_SCHEDULER_shutdown();
872 GNUNET_DISK_file_size(fn, &left, GNUNET_YES, GNUNET_YES))
874 while (left > sizeof(struct RevokeMessage))
876 rm = GNUNET_new(struct RevokeMessage);
877 if (sizeof(struct RevokeMessage) !=
878 GNUNET_DISK_file_read(revocation_db,
880 sizeof(struct RevokeMessage)))
882 GNUNET_log_strerror_file(GNUNET_ERROR_TYPE_ERROR,
886 GNUNET_SCHEDULER_shutdown();
890 GNUNET_break(0 == ntohl(rm->reserved));
891 GNUNET_CRYPTO_hash(&rm->public_key,
892 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
894 GNUNET_break(GNUNET_OK ==
895 GNUNET_CONTAINER_multihashmap_put(revocation_map,
898 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
902 GNUNET_SCHEDULER_add_shutdown(&shutdown_task,
904 peers = GNUNET_CONTAINER_multipeermap_create(128,
906 /* Connect to core service and register core handlers */
907 core_api = GNUNET_CORE_connect(cfg, /* Main configuration */
908 NULL, /* Closure passed to functions */
909 &core_init, /* Call core_init once connected */
910 &handle_core_connect, /* Handle connects */
911 &handle_core_disconnect, /* Handle disconnects */
912 core_handlers); /* Register these handlers */
913 if (NULL == core_api)
915 GNUNET_SCHEDULER_shutdown();
918 stats = GNUNET_STATISTICS_create("revocation",
924 * Define "main" method using service macro.
928 GNUNET_SERVICE_OPTION_NONE,
931 &client_disconnect_cb,
933 GNUNET_MQ_hd_fixed_size(query_message,
934 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY,
937 GNUNET_MQ_hd_fixed_size(revoke_message,
938 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
939 struct RevokeMessage,
941 GNUNET_MQ_handler_end());
944 #if defined(LINUX) && defined(__GLIBC__)
948 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
950 void __attribute__ ((constructor))
951 GNUNET_REVOCATION_memory_init()
953 mallopt(M_TRIM_THRESHOLD, 4 * 1024);
954 mallopt(M_TOP_PAD, 1 * 1024);
961 /* end of gnunet-service-revocation.c */