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
6 it under the terms of the GNU General Public Licerevocation as published
7 by the Free Software Foundation; either version 3, or (at your
8 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 General Public Licerevocation for more details.
15 You should have received a copy of the GNU General Public Licerevocation
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
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_constants.h"
42 #include "gnunet_protocols.h"
43 #include "gnunet_signatures.h"
44 #include "gnunet_statistics_service.h"
45 #include "gnunet_core_service.h"
46 #include "gnunet_revocation_service.h"
47 #include "gnunet_set_service.h"
48 #include "revocation.h"
53 * 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;
82 * Set from all revocations known to us.
84 static struct GNUNET_SET_Handle *revocation_set;
87 * Hash map with all revoked keys, maps the hash of the public key
88 * to the respective `struct RevokeMessage`.
90 static struct GNUNET_CONTAINER_MultiHashMap *revocation_map;
93 * Handle to our current configuration.
95 static const struct GNUNET_CONFIGURATION_Handle *cfg;
98 * Handle to the statistics service.
100 static struct GNUNET_STATISTICS_Handle *stats;
103 * Handle to the core service (for flooding)
105 static struct GNUNET_CORE_Handle *core_api;
108 * Map of all connected peers.
110 static struct GNUNET_CONTAINER_MultiPeerMap *peers;
113 * The peer identity of this peer.
115 static struct GNUNET_PeerIdentity my_identity;
118 * File handle for the revocation database.
120 static struct GNUNET_DISK_FileHandle *revocation_db;
123 * Handle for us listening to incoming revocation set union requests.
125 static struct GNUNET_SET_ListenHandle *revocation_union_listen_handle;
128 * Amount of work required (W-bit collisions) for REVOCATION proofs, in collision-bits.
130 static unsigned long long revocation_work_required;
133 * Our application ID for set union operations. Must be the
134 * same for all (compatible) peers.
136 static struct GNUNET_HashCode revocation_set_union_app_id;
140 * Create a new PeerEntry and add it to the peers multipeermap.
142 * @param peer the peer identity
143 * @return a pointer to the new PeerEntry
145 static struct PeerEntry *
146 new_peer_entry(const struct GNUNET_PeerIdentity *peer)
148 struct PeerEntry *peer_entry;
150 peer_entry = GNUNET_new (struct PeerEntry);
151 peer_entry->id = *peer;
152 GNUNET_assert (GNUNET_OK ==
153 GNUNET_CONTAINER_multipeermap_put (peers,
156 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
162 * An revoke message has been received, check that it is well-formed.
164 * @param rm the message to verify
165 * @return #GNUNET_YES if the message is verified
166 * #GNUNET_NO if the key/signature don't verify
169 verify_revoke_message (const struct RevokeMessage *rm)
172 GNUNET_REVOCATION_check_pow (&rm->public_key,
174 (unsigned int) revocation_work_required))
176 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
177 "Proof of work invalid!\n");
182 GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_REVOCATION,
195 * Handle client connecting to the service.
198 * @param client the new client
199 * @param mq the message queue of @a client
203 client_connect_cb (void *cls,
204 struct GNUNET_SERVICE_Client *client,
205 struct GNUNET_MQ_Handle *mq)
212 * Handle client connecting to the service.
215 * @param client the new client
216 * @param app_cls must alias @a client
219 client_disconnect_cb (void *cls,
220 struct GNUNET_SERVICE_Client *client,
223 GNUNET_assert (client == app_cls);
228 * Handle QUERY message from client.
230 * @param cls client who sent the message
231 * @param qm the message received
234 handle_query_message (void *cls,
235 const struct QueryMessage *qm)
237 struct GNUNET_SERVICE_Client *client = cls;
238 struct GNUNET_MQ_Envelope *env;
239 struct QueryResponseMessage *qrm;
240 struct GNUNET_HashCode hc;
243 GNUNET_CRYPTO_hash (&qm->key,
244 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
246 res = GNUNET_CONTAINER_multihashmap_contains (revocation_map,
248 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
250 ? "Received revocation check for valid key `%s' from client\n"
251 : "Received revocation check for revoked key `%s' from client\n",
253 env = GNUNET_MQ_msg (qrm,
254 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY_RESPONSE);
255 qrm->is_valid = htonl ((GNUNET_YES == res) ? GNUNET_NO : GNUNET_YES);
256 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
258 GNUNET_SERVICE_client_continue (client);
263 * Flood the given revocation message to all neighbours.
265 * @param cls the `struct RevokeMessage` to flood
266 * @param target a neighbour
267 * @param value our `struct PeerEntry` for the neighbour
268 * @return #GNUNET_OK (continue to iterate)
272 const struct GNUNET_PeerIdentity *target,
275 const struct RevokeMessage *rm = cls;
276 struct PeerEntry *pe = value;
277 struct GNUNET_MQ_Envelope *e;
278 struct RevokeMessage *cp;
281 return GNUNET_OK; /* peer connected to us via SET,
282 but we have no direct CORE
283 connection for flooding */
284 e = GNUNET_MQ_msg (cp,
285 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE);
287 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
288 "Flooding revocation to `%s'\n",
289 GNUNET_i2s (target));
290 GNUNET_MQ_send (pe->mq,
297 * Publicize revocation message. Stores the message locally in the
298 * database and passes it to all connected neighbours (and adds it to
299 * the set for future connections).
301 * @param rm message to publicize
302 * @return #GNUNET_OK on success, #GNUNET_NO if we encountered an error,
303 * #GNUNET_SYSERR if the message was malformed
306 publicize_rm (const struct RevokeMessage *rm)
308 struct RevokeMessage *cp;
309 struct GNUNET_HashCode hc;
310 struct GNUNET_SET_Element e;
312 GNUNET_CRYPTO_hash (&rm->public_key,
313 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
316 GNUNET_CONTAINER_multihashmap_contains (revocation_map,
319 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
320 "Duplicate revocation received from peer. Ignored.\n");
324 verify_revoke_message (rm))
327 return GNUNET_SYSERR;
330 if (sizeof (struct RevokeMessage) !=
331 GNUNET_DISK_file_write (revocation_db,
333 sizeof (struct RevokeMessage)))
335 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
340 GNUNET_DISK_file_sync (revocation_db))
342 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
346 /* keep copy in memory */
347 cp = (struct RevokeMessage *) GNUNET_copy_message (&rm->header);
348 GNUNET_break (GNUNET_OK ==
349 GNUNET_CONTAINER_multihashmap_put (revocation_map,
352 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
353 /* add to set for future connections */
354 e.size = htons (rm->header.size);
358 GNUNET_SET_add_element (revocation_set,
368 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
369 "Added revocation info to SET\n");
371 /* flood to neighbours */
372 GNUNET_CONTAINER_multipeermap_iterate (peers,
380 * Handle REVOKE message from client.
382 * @param cls client who sent the message
383 * @param rm the message received
386 handle_revoke_message (void *cls,
387 const struct RevokeMessage *rm)
389 struct GNUNET_SERVICE_Client *client = cls;
390 struct GNUNET_MQ_Envelope *env;
391 struct RevocationResponseMessage *rrm;
394 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
395 "Received REVOKE message from client\n");
396 if (GNUNET_SYSERR == (ret = publicize_rm (rm)))
399 GNUNET_SERVICE_client_drop (client);
402 env = GNUNET_MQ_msg (rrm,
403 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE_RESPONSE);
404 rrm->is_valid = htonl ((GNUNET_OK == ret) ? GNUNET_NO : GNUNET_YES);
405 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
407 GNUNET_SERVICE_client_continue (client);
412 * Core handler for flooded revocation messages.
414 * @param cls closure unused
415 * @param rm revocation message
418 handle_p2p_revoke (void *cls,
419 const struct RevokeMessage *rm)
421 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
422 "Received REVOKE message\n");
423 GNUNET_break_op (GNUNET_SYSERR !=
429 * Callback for set operation results. Called for each element in the
430 * result set. Each element contains a revocation, which we should
431 * validate and then add to our revocation list (and set).
434 * @param element a result element, only valid if status is #GNUNET_SET_STATUS_OK
435 * @param status see `enum GNUNET_SET_Status`
438 add_revocation (void *cls,
439 const struct GNUNET_SET_Element *element,
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 (0 != element->element_type)
455 GNUNET_STATISTICS_update (stats,
456 gettext_noop ("# unsupported revocations received via set union"),
461 (void) handle_p2p_revoke (NULL,
463 GNUNET_STATISTICS_update (stats,
464 gettext_noop ("# revocation messages received via set union"),
467 case GNUNET_SET_STATUS_FAILURE:
468 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
469 _("Error computing revocation set union with %s\n"),
470 GNUNET_i2s (&peer_entry->id));
471 peer_entry->so = NULL;
472 GNUNET_STATISTICS_update (stats,
473 gettext_noop ("# revocation set unions failed"),
477 case GNUNET_SET_STATUS_HALF_DONE:
479 case GNUNET_SET_STATUS_DONE:
480 peer_entry->so = NULL;
481 GNUNET_STATISTICS_update (stats,
482 gettext_noop ("# revocation set unions completed"),
494 * The timeout for performing the set union has expired,
495 * run the set operation on the revocation certificates.
500 transmit_task_cb (void *cls)
502 struct PeerEntry *peer_entry = cls;
504 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
505 "Starting set exchange with peer `%s'\n",
506 GNUNET_i2s (&peer_entry->id));
507 peer_entry->transmit_task = NULL;
508 peer_entry->so = GNUNET_SET_prepare (&peer_entry->id,
509 &revocation_set_union_app_id,
511 GNUNET_SET_RESULT_ADDED,
515 GNUNET_SET_commit (peer_entry->so,
518 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
519 _("SET service crashed, terminating revocation service\n"));
520 GNUNET_SCHEDULER_shutdown ();
527 * Method called whenever a peer connects. Sets up the PeerEntry and
528 * schedules the initial revocation set exchange with this peer.
531 * @param peer peer identity this notification is about
534 handle_core_connect (void *cls,
535 const struct GNUNET_PeerIdentity *peer,
536 struct GNUNET_MQ_Handle *mq)
538 struct PeerEntry *peer_entry;
539 struct GNUNET_HashCode my_hash;
540 struct GNUNET_HashCode peer_hash;
542 if (0 == memcmp (peer,
544 sizeof (my_identity)))
549 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
550 "Peer `%s' connected to us\n",
552 GNUNET_STATISTICS_update (stats,
556 peer_entry = GNUNET_CONTAINER_multipeermap_get (peers,
558 if (NULL != peer_entry)
560 /* This can happen if "core"'s notification is a tad late
561 and CADET+SET were faster and already produced a
562 #handle_revocation_union_request() for us to deal
563 with. This should be rare, but isn't impossible. */
567 peer_entry = new_peer_entry (peer);
569 GNUNET_CRYPTO_hash (&my_identity,
570 sizeof (my_identity),
572 GNUNET_CRYPTO_hash (peer,
575 if (0 < GNUNET_CRYPTO_hash_cmp (&my_hash,
578 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
579 "Starting SET operation with peer `%s'\n",
581 peer_entry->transmit_task =
582 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
591 * Method called whenever a peer disconnects. Deletes the PeerEntry and cancels
592 * any pending transmission requests to that peer.
595 * @param peer peer identity this notification is about
596 * @param internal_cls our `struct PeerEntry` for this peer
599 handle_core_disconnect (void *cls,
600 const struct GNUNET_PeerIdentity *peer,
603 struct PeerEntry *peer_entry = internal_cls;
605 if (0 == memcmp (peer,
607 sizeof (my_identity)))
609 GNUNET_assert (NULL != peer_entry);
610 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
611 "Peer `%s' disconnected from us\n",
613 GNUNET_assert (GNUNET_YES ==
614 GNUNET_CONTAINER_multipeermap_remove (peers,
617 if (NULL != peer_entry->transmit_task)
619 GNUNET_SCHEDULER_cancel (peer_entry->transmit_task);
620 peer_entry->transmit_task = NULL;
622 if (NULL != peer_entry->so)
624 GNUNET_SET_operation_cancel (peer_entry->so);
625 peer_entry->so = NULL;
627 GNUNET_free (peer_entry);
628 GNUNET_STATISTICS_update (stats,
636 * Free all values in a hash map.
640 * @param value value to free
641 * @return #GNUNET_OK (continue to iterate)
644 free_entry (void *cls,
645 const struct GNUNET_HashCode *key,
654 * Task run during shutdown.
659 shutdown_task (void *cls)
661 if (NULL != revocation_set)
663 GNUNET_SET_destroy (revocation_set);
664 revocation_set = NULL;
666 if (NULL != revocation_union_listen_handle)
668 GNUNET_SET_listen_cancel (revocation_union_listen_handle);
669 revocation_union_listen_handle = NULL;
671 if (NULL != core_api)
673 GNUNET_CORE_disconnecT (core_api);
678 GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
683 GNUNET_CONTAINER_multipeermap_destroy (peers);
686 if (NULL != revocation_db)
688 GNUNET_DISK_file_close (revocation_db);
689 revocation_db = NULL;
691 GNUNET_CONTAINER_multihashmap_iterate (revocation_map,
694 GNUNET_CONTAINER_multihashmap_destroy (revocation_map);
699 * Called on core init/fail.
701 * @param cls service closure
702 * @param identity the public identity of this peer
705 core_init (void *cls,
706 const struct GNUNET_PeerIdentity *identity)
708 if (NULL == identity)
710 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
711 "Connection to core FAILED!\n");
712 GNUNET_SCHEDULER_shutdown ();
715 my_identity = *identity;
720 * Called when another peer wants to do a set operation with the
721 * local peer. If a listen error occurs, the 'request' is NULL.
724 * @param other_peer the other peer
725 * @param context_msg message with application specific information from
727 * @param request request from the other peer (never NULL), use GNUNET_SET_accept()
728 * to accept it, otherwise the request will be refused
729 * Note that we can't just return value from the listen callback,
730 * as it is also necessary to specify the set we want to do the
731 * operation with, whith sometimes can be derived from the context
732 * message. It's necessary to specify the timeout.
735 handle_revocation_union_request (void *cls,
736 const struct GNUNET_PeerIdentity *other_peer,
737 const struct GNUNET_MessageHeader *context_msg,
738 struct GNUNET_SET_Request *request)
740 struct PeerEntry *peer_entry;
747 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
748 "Received set exchange request from peer `%s'\n",
749 GNUNET_i2s (other_peer));
750 peer_entry = GNUNET_CONTAINER_multipeermap_get (peers,
752 if (NULL == peer_entry)
754 peer_entry = new_peer_entry (other_peer);
756 peer_entry->so = GNUNET_SET_accept (request,
757 GNUNET_SET_RESULT_ADDED,
761 GNUNET_SET_commit (peer_entry->so,
764 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
765 _("SET service crashed, terminating revocation service\n"));
766 GNUNET_SCHEDULER_shutdown ();
773 * Handle network size estimate clients.
776 * @param server the initialized server
777 * @param c configuration to use
781 const struct GNUNET_CONFIGURATION_Handle *c,
782 struct GNUNET_SERVICE_Handle *service)
784 struct GNUNET_MQ_MessageHandler core_handlers[] = {
785 GNUNET_MQ_hd_fixed_size (p2p_revoke,
786 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
787 struct RevokeMessage,
789 GNUNET_MQ_handler_end ()
793 struct RevokeMessage *rm;
794 struct GNUNET_HashCode hc;
796 GNUNET_CRYPTO_hash ("revocation-set-union-application-id",
797 strlen ("revocation-set-union-application-id"),
798 &revocation_set_union_app_id);
800 GNUNET_CONFIGURATION_get_value_filename (c,
805 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
808 GNUNET_SCHEDULER_shutdown ();
812 revocation_map = GNUNET_CONTAINER_multihashmap_create (16,
815 GNUNET_CONFIGURATION_get_value_number (cfg,
818 &revocation_work_required))
820 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
823 GNUNET_SCHEDULER_shutdown ();
827 if (revocation_work_required >= sizeof (struct GNUNET_HashCode) * 8)
829 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
832 _("Value is too large.\n"));
833 GNUNET_SCHEDULER_shutdown ();
837 revocation_set = GNUNET_SET_create (cfg,
838 GNUNET_SET_OPERATION_UNION);
839 revocation_union_listen_handle
840 = GNUNET_SET_listen (cfg,
841 GNUNET_SET_OPERATION_UNION,
842 &revocation_set_union_app_id,
843 &handle_revocation_union_request,
845 revocation_db = GNUNET_DISK_file_open (fn,
846 GNUNET_DISK_OPEN_READWRITE |
847 GNUNET_DISK_OPEN_CREATE,
848 GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE |
849 GNUNET_DISK_PERM_GROUP_READ |
850 GNUNET_DISK_PERM_OTHER_READ);
851 if (NULL == revocation_db)
853 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
856 _("Could not open revocation database file!"));
857 GNUNET_SCHEDULER_shutdown ();
862 GNUNET_DISK_file_size (fn, &left, GNUNET_YES, GNUNET_YES))
864 while (left > sizeof (struct RevokeMessage))
866 rm = GNUNET_new (struct RevokeMessage);
867 if (sizeof (struct RevokeMessage) !=
868 GNUNET_DISK_file_read (revocation_db,
870 sizeof (struct RevokeMessage)))
872 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
876 GNUNET_SCHEDULER_shutdown ();
880 GNUNET_break (0 == ntohl (rm->reserved));
881 GNUNET_CRYPTO_hash (&rm->public_key,
882 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
884 GNUNET_break (GNUNET_OK ==
885 GNUNET_CONTAINER_multihashmap_put (revocation_map,
888 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
892 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
894 peers = GNUNET_CONTAINER_multipeermap_create (128,
896 /* Connect to core service and register core handlers */
897 core_api = GNUNET_CORE_connecT (cfg, /* Main configuration */
898 NULL, /* Closure passed to functions */
899 &core_init, /* Call core_init once connected */
900 &handle_core_connect, /* Handle connects */
901 &handle_core_disconnect, /* Handle disconnects */
902 core_handlers); /* Register these handlers */
903 if (NULL == core_api)
905 GNUNET_SCHEDULER_shutdown ();
908 stats = GNUNET_STATISTICS_create ("revocation",
914 * Define "main" method using service macro.
918 GNUNET_SERVICE_OPTION_NONE,
921 &client_disconnect_cb,
923 GNUNET_MQ_hd_fixed_size (query_message,
924 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY,
927 GNUNET_MQ_hd_fixed_size (revoke_message,
928 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
929 struct RevokeMessage,
931 GNUNET_MQ_handler_end ());
934 #if defined(LINUX) && defined(__GLIBC__)
938 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
940 void __attribute__ ((constructor))
941 GNUNET_REVOCATION_memory_init ()
943 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
944 mallopt (M_TOP_PAD, 1 * 1024);
951 /* end of gnunet-service-revocation.c */