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.
59 * Queue for sending messages to this peer.
61 struct GNUNET_MQ_Handle *mq;
64 * What is the identity of the peer?
66 struct GNUNET_PeerIdentity id;
69 * Tasked used to trigger the set union operation.
71 struct GNUNET_SCHEDULER_Task *transmit_task;
74 * Handle to active set union operation (over revocation sets).
76 struct GNUNET_SET_OperationHandle *so;
81 * Set from all revocations known to us.
83 static struct GNUNET_SET_Handle *revocation_set;
86 * Hash map with all revoked keys, maps the hash of the public key
87 * to the respective `struct RevokeMessage`.
89 static struct GNUNET_CONTAINER_MultiHashMap *revocation_map;
92 * Handle to our current configuration.
94 static const struct GNUNET_CONFIGURATION_Handle *cfg;
97 * Handle to the statistics service.
99 static struct GNUNET_STATISTICS_Handle *stats;
102 * Handle to the core service (for flooding)
104 static struct GNUNET_CORE_Handle *core_api;
107 * Map of all connected peers.
109 static struct GNUNET_CONTAINER_MultiPeerMap *peers;
112 * The peer identity of this peer.
114 static struct GNUNET_PeerIdentity my_identity;
117 * File handle for the revocation database.
119 static struct GNUNET_DISK_FileHandle *revocation_db;
122 * Handle for us listening to incoming revocation set union requests.
124 static struct GNUNET_SET_ListenHandle *revocation_union_listen_handle;
127 * Amount of work required (W-bit collisions) for REVOCATION proofs, in collision-bits.
129 static unsigned long long revocation_work_required;
132 * Length of an expiration expoch
134 static struct GNUNET_TIME_Relative epoch_duration;
137 * Our application ID for set union operations. Must be the
138 * same for all (compatible) peers.
140 static struct GNUNET_HashCode revocation_set_union_app_id;
144 * Create a new PeerEntry and add it to the peers multipeermap.
146 * @param peer the peer identity
147 * @return a pointer to the new PeerEntry
149 static struct PeerEntry *
150 new_peer_entry (const struct GNUNET_PeerIdentity *peer)
152 struct PeerEntry *peer_entry;
154 peer_entry = GNUNET_new (struct PeerEntry);
155 peer_entry->id = *peer;
156 GNUNET_assert (GNUNET_OK ==
157 GNUNET_CONTAINER_multipeermap_put (peers,
160 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
166 * An revoke message has been received, check that it is well-formed.
168 * @param rm the message to verify
169 * @return #GNUNET_YES if the message is verified
170 * #GNUNET_NO if the key/signature don't verify
173 verify_revoke_message (const struct RevokeMessage *rm)
175 if (GNUNET_YES != GNUNET_REVOCATION_check_pow (&rm->proof_of_work,
177 int) revocation_work_required,
180 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
181 "Proof of work invalid!\n");
190 * Handle client connecting to the service.
193 * @param client the new client
194 * @param mq the message queue of @a client
198 client_connect_cb (void *cls,
199 struct GNUNET_SERVICE_Client *client,
200 struct GNUNET_MQ_Handle *mq)
207 * Handle client connecting to the service.
210 * @param client the new client
211 * @param app_cls must alias @a client
214 client_disconnect_cb (void *cls,
215 struct GNUNET_SERVICE_Client *client,
218 GNUNET_assert (client == app_cls);
223 * Handle QUERY message from client.
225 * @param cls client who sent the message
226 * @param qm the message received
229 handle_query_message (void *cls,
230 const struct QueryMessage *qm)
232 struct GNUNET_SERVICE_Client *client = cls;
233 struct GNUNET_MQ_Envelope *env;
234 struct QueryResponseMessage *qrm;
235 struct GNUNET_HashCode hc;
238 GNUNET_CRYPTO_hash (&qm->key,
239 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
241 res = GNUNET_CONTAINER_multihashmap_contains (revocation_map,
243 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
245 ? "Received revocation check for valid key `%s' from client\n"
246 : "Received revocation check for revoked key `%s' from client\n",
248 env = GNUNET_MQ_msg (qrm,
249 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY_RESPONSE);
250 qrm->is_valid = htonl ((GNUNET_YES == res) ? GNUNET_NO : GNUNET_YES);
251 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
253 GNUNET_SERVICE_client_continue (client);
258 * Flood the given revocation message to all neighbours.
260 * @param cls the `struct RevokeMessage` to flood
261 * @param target a neighbour
262 * @param value our `struct PeerEntry` for the neighbour
263 * @return #GNUNET_OK (continue to iterate)
267 const struct GNUNET_PeerIdentity *target,
270 const struct RevokeMessage *rm = cls;
271 struct PeerEntry *pe = value;
272 struct GNUNET_MQ_Envelope *e;
273 struct RevokeMessage *cp;
276 return GNUNET_OK; /* peer connected to us via SET,
277 but we have no direct CORE
278 connection for flooding */
279 e = GNUNET_MQ_msg (cp,
280 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE);
282 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
283 "Flooding revocation to `%s'\n",
284 GNUNET_i2s (target));
285 GNUNET_MQ_send (pe->mq,
292 * Publicize revocation message. Stores the message locally in the
293 * database and passes it to all connected neighbours (and adds it to
294 * the set for future connections).
296 * @param rm message to publicize
297 * @return #GNUNET_OK on success, #GNUNET_NO if we encountered an error,
298 * #GNUNET_SYSERR if the message was malformed
301 publicize_rm (const struct RevokeMessage *rm)
303 struct RevokeMessage *cp;
304 struct GNUNET_HashCode hc;
305 struct GNUNET_SET_Element e;
307 GNUNET_CRYPTO_hash (&rm->proof_of_work.key,
308 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
311 GNUNET_CONTAINER_multihashmap_contains (revocation_map,
314 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
315 "Duplicate revocation received from peer. Ignored.\n");
319 verify_revoke_message (rm))
322 return GNUNET_SYSERR;
325 if (sizeof(struct RevokeMessage) !=
326 GNUNET_DISK_file_write (revocation_db,
328 sizeof(struct RevokeMessage)))
330 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
335 GNUNET_DISK_file_sync (revocation_db))
337 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
341 /* keep copy in memory */
342 cp = (struct RevokeMessage *) GNUNET_copy_message (&rm->header);
343 GNUNET_break (GNUNET_OK ==
344 GNUNET_CONTAINER_multihashmap_put (revocation_map,
347 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
348 /* add to set for future connections */
349 e.size = htons (rm->header.size);
350 e.element_type = GNUNET_BLOCK_TYPE_REVOCATION;
353 GNUNET_SET_add_element (revocation_set,
363 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
364 "Added revocation info to SET\n");
366 /* flood to neighbours */
367 GNUNET_CONTAINER_multipeermap_iterate (peers,
375 * Handle REVOKE message from client.
377 * @param cls client who sent the message
378 * @param rm the message received
381 handle_revoke_message (void *cls,
382 const struct RevokeMessage *rm)
384 struct GNUNET_SERVICE_Client *client = cls;
385 struct GNUNET_MQ_Envelope *env;
386 struct RevocationResponseMessage *rrm;
389 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
390 "Received REVOKE message from client\n");
391 if (GNUNET_SYSERR == (ret = publicize_rm (rm)))
394 GNUNET_SERVICE_client_drop (client);
397 env = GNUNET_MQ_msg (rrm,
398 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE_RESPONSE);
399 rrm->is_valid = htonl ((GNUNET_OK == ret) ? GNUNET_NO : GNUNET_YES);
400 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
402 GNUNET_SERVICE_client_continue (client);
407 * Core handler for flooded revocation messages.
409 * @param cls closure unused
410 * @param rm revocation message
413 handle_p2p_revoke (void *cls,
414 const struct RevokeMessage *rm)
416 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
417 "Received REVOKE message\n");
418 GNUNET_break_op (GNUNET_SYSERR !=
424 * Callback for set operation results. Called for each element in the
425 * result set. Each element contains a revocation, which we should
426 * validate and then add to our revocation list (and set).
429 * @param element a result element, only valid if status is #GNUNET_SET_STATUS_OK
430 * @param current_size current set size
431 * @param status see `enum GNUNET_SET_Status`
434 add_revocation (void *cls,
435 const struct GNUNET_SET_Element *element,
436 uint64_t current_size,
437 enum GNUNET_SET_Status status)
439 struct PeerEntry *peer_entry = cls;
440 const struct RevokeMessage *rm;
444 case GNUNET_SET_STATUS_OK:
445 if (element->size != sizeof(struct RevokeMessage))
450 if (GNUNET_BLOCK_TYPE_REVOCATION != element->element_type)
452 GNUNET_STATISTICS_update (stats,
454 "# unsupported revocations received via set union"),
460 (void) handle_p2p_revoke (NULL,
462 GNUNET_STATISTICS_update (stats,
464 "# revocation messages received via set union"),
468 case GNUNET_SET_STATUS_FAILURE:
469 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
470 _ ("Error computing revocation set union with %s\n"),
471 GNUNET_i2s (&peer_entry->id));
472 peer_entry->so = NULL;
473 GNUNET_STATISTICS_update (stats,
474 gettext_noop ("# revocation set unions failed"),
479 case GNUNET_SET_STATUS_HALF_DONE:
482 case GNUNET_SET_STATUS_DONE:
483 peer_entry->so = NULL;
484 GNUNET_STATISTICS_update (stats,
486 "# 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
710 core_init (void *cls,
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 ();
848 GNUNET_CONFIGURATION_get_value_time (cfg,
853 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
856 GNUNET_SCHEDULER_shutdown ();
861 revocation_set = GNUNET_SET_create (cfg,
862 GNUNET_SET_OPERATION_UNION);
863 revocation_union_listen_handle
864 = GNUNET_SET_listen (cfg,
865 GNUNET_SET_OPERATION_UNION,
866 &revocation_set_union_app_id,
867 &handle_revocation_union_request,
869 revocation_db = GNUNET_DISK_file_open (fn,
870 GNUNET_DISK_OPEN_READWRITE
871 | GNUNET_DISK_OPEN_CREATE,
872 GNUNET_DISK_PERM_USER_READ
873 | GNUNET_DISK_PERM_USER_WRITE
874 | GNUNET_DISK_PERM_GROUP_READ
875 | GNUNET_DISK_PERM_OTHER_READ);
876 if (NULL == revocation_db)
878 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
881 _ ("Could not open revocation database file!"));
882 GNUNET_SCHEDULER_shutdown ();
887 GNUNET_DISK_file_size (fn, &left, GNUNET_YES, GNUNET_YES))
889 while (left > sizeof(struct RevokeMessage))
891 rm = GNUNET_new (struct RevokeMessage);
892 if (sizeof(struct RevokeMessage) !=
893 GNUNET_DISK_file_read (revocation_db,
895 sizeof(struct RevokeMessage)))
897 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
901 GNUNET_SCHEDULER_shutdown ();
905 GNUNET_break (0 == ntohl (rm->reserved));
906 GNUNET_CRYPTO_hash (&rm->proof_of_work.key,
907 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
909 GNUNET_break (GNUNET_OK ==
910 GNUNET_CONTAINER_multihashmap_put (revocation_map,
913 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
917 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
919 peers = GNUNET_CONTAINER_multipeermap_create (128,
921 /* Connect to core service and register core handlers */
922 core_api = GNUNET_CORE_connect (cfg, /* Main configuration */
923 NULL, /* Closure passed to functions */
924 &core_init, /* Call core_init once connected */
925 &handle_core_connect, /* Handle connects */
926 &handle_core_disconnect, /* Handle disconnects */
927 core_handlers); /* Register these handlers */
928 if (NULL == core_api)
930 GNUNET_SCHEDULER_shutdown ();
933 stats = GNUNET_STATISTICS_create ("revocation",
939 * Define "main" method using service macro.
943 GNUNET_SERVICE_OPTION_NONE,
946 &client_disconnect_cb,
948 GNUNET_MQ_hd_fixed_size (query_message,
949 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY,
952 GNUNET_MQ_hd_fixed_size (revoke_message,
953 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
954 struct RevokeMessage,
956 GNUNET_MQ_handler_end ());
959 #if defined(__linux__) && defined(__GLIBC__)
963 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
965 void __attribute__ ((constructor))
966 GNUNET_REVOCATION_memory_init ()
968 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
969 mallopt (M_TOP_PAD, 1 * 1024);
977 /* end of gnunet-service-revocation.c */