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/>.
20 * @file revocation/gnunet-service-revocation.c
21 * @brief key revocation service
22 * @author Christian Grothoff
24 * The purpose of this service is to allow users to permanently revoke
25 * (compromised) keys. This is done by flooding the network with the
26 * revocation requests. To reduce the attack potential offered by such
27 * flooding, revocations must include a proof of work. We use the
28 * set service for efficiently computing the union of revocations of
32 * - optimization: avoid sending revocation back to peer that we got it from;
33 * - optimization: have randomized delay in sending revocations to other peers
34 * to make it rare to traverse each link twice (NSE-style)
38 #include "gnunet_util_lib.h"
39 #include "gnunet_block_lib.h"
40 #include "gnunet_constants.h"
41 #include "gnunet_protocols.h"
42 #include "gnunet_signatures.h"
43 #include "gnunet_statistics_service.h"
44 #include "gnunet_core_service.h"
45 #include "gnunet_revocation_service.h"
46 #include "gnunet_set_service.h"
47 #include "revocation.h"
52 * 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;
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 * Our application ID for set union operations. Must be the
133 * same for all (compatible) peers.
135 static struct GNUNET_HashCode revocation_set_union_app_id;
139 * Create a new PeerEntry and add it to the peers multipeermap.
141 * @param peer the peer identity
142 * @return a pointer to the new PeerEntry
144 static struct PeerEntry *
145 new_peer_entry(const struct GNUNET_PeerIdentity *peer)
147 struct PeerEntry *peer_entry;
149 peer_entry = GNUNET_new (struct PeerEntry);
150 peer_entry->id = *peer;
151 GNUNET_assert (GNUNET_OK ==
152 GNUNET_CONTAINER_multipeermap_put (peers,
155 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
161 * An revoke message has been received, check that it is well-formed.
163 * @param rm the message to verify
164 * @return #GNUNET_YES if the message is verified
165 * #GNUNET_NO if the key/signature don't verify
168 verify_revoke_message (const struct RevokeMessage *rm)
171 GNUNET_REVOCATION_check_pow (&rm->public_key,
173 (unsigned int) revocation_work_required))
175 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
176 "Proof of work invalid!\n");
181 GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_REVOCATION,
194 * Handle client connecting to the service.
197 * @param client the new client
198 * @param mq the message queue of @a client
202 client_connect_cb (void *cls,
203 struct GNUNET_SERVICE_Client *client,
204 struct GNUNET_MQ_Handle *mq)
211 * Handle client connecting to the service.
214 * @param client the new client
215 * @param app_cls must alias @a client
218 client_disconnect_cb (void *cls,
219 struct GNUNET_SERVICE_Client *client,
222 GNUNET_assert (client == app_cls);
227 * Handle QUERY message from client.
229 * @param cls client who sent the message
230 * @param qm the message received
233 handle_query_message (void *cls,
234 const struct QueryMessage *qm)
236 struct GNUNET_SERVICE_Client *client = cls;
237 struct GNUNET_MQ_Envelope *env;
238 struct QueryResponseMessage *qrm;
239 struct GNUNET_HashCode hc;
242 GNUNET_CRYPTO_hash (&qm->key,
243 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
245 res = GNUNET_CONTAINER_multihashmap_contains (revocation_map,
247 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
249 ? "Received revocation check for valid key `%s' from client\n"
250 : "Received revocation check for revoked key `%s' from client\n",
252 env = GNUNET_MQ_msg (qrm,
253 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY_RESPONSE);
254 qrm->is_valid = htonl ((GNUNET_YES == res) ? GNUNET_NO : GNUNET_YES);
255 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
257 GNUNET_SERVICE_client_continue (client);
262 * Flood the given revocation message to all neighbours.
264 * @param cls the `struct RevokeMessage` to flood
265 * @param target a neighbour
266 * @param value our `struct PeerEntry` for the neighbour
267 * @return #GNUNET_OK (continue to iterate)
271 const struct GNUNET_PeerIdentity *target,
274 const struct RevokeMessage *rm = cls;
275 struct PeerEntry *pe = value;
276 struct GNUNET_MQ_Envelope *e;
277 struct RevokeMessage *cp;
280 return GNUNET_OK; /* peer connected to us via SET,
281 but we have no direct CORE
282 connection for flooding */
283 e = GNUNET_MQ_msg (cp,
284 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE);
286 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
287 "Flooding revocation to `%s'\n",
288 GNUNET_i2s (target));
289 GNUNET_MQ_send (pe->mq,
296 * Publicize revocation message. Stores the message locally in the
297 * database and passes it to all connected neighbours (and adds it to
298 * the set for future connections).
300 * @param rm message to publicize
301 * @return #GNUNET_OK on success, #GNUNET_NO if we encountered an error,
302 * #GNUNET_SYSERR if the message was malformed
305 publicize_rm (const struct RevokeMessage *rm)
307 struct RevokeMessage *cp;
308 struct GNUNET_HashCode hc;
309 struct GNUNET_SET_Element e;
311 GNUNET_CRYPTO_hash (&rm->public_key,
312 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
315 GNUNET_CONTAINER_multihashmap_contains (revocation_map,
318 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
319 "Duplicate revocation received from peer. Ignored.\n");
323 verify_revoke_message (rm))
326 return GNUNET_SYSERR;
329 if (sizeof (struct RevokeMessage) !=
330 GNUNET_DISK_file_write (revocation_db,
332 sizeof (struct RevokeMessage)))
334 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
339 GNUNET_DISK_file_sync (revocation_db))
341 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
345 /* keep copy in memory */
346 cp = (struct RevokeMessage *) GNUNET_copy_message (&rm->header);
347 GNUNET_break (GNUNET_OK ==
348 GNUNET_CONTAINER_multihashmap_put (revocation_map,
351 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
352 /* add to set for future connections */
353 e.size = htons (rm->header.size);
354 e.element_type = GNUNET_BLOCK_TYPE_REVOCATION;
357 GNUNET_SET_add_element (revocation_set,
367 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
368 "Added revocation info to SET\n");
370 /* flood to neighbours */
371 GNUNET_CONTAINER_multipeermap_iterate (peers,
379 * Handle REVOKE message from client.
381 * @param cls client who sent the message
382 * @param rm the message received
385 handle_revoke_message (void *cls,
386 const struct RevokeMessage *rm)
388 struct GNUNET_SERVICE_Client *client = cls;
389 struct GNUNET_MQ_Envelope *env;
390 struct RevocationResponseMessage *rrm;
393 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
394 "Received REVOKE message from client\n");
395 if (GNUNET_SYSERR == (ret = publicize_rm (rm)))
398 GNUNET_SERVICE_client_drop (client);
401 env = GNUNET_MQ_msg (rrm,
402 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE_RESPONSE);
403 rrm->is_valid = htonl ((GNUNET_OK == ret) ? GNUNET_NO : GNUNET_YES);
404 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
406 GNUNET_SERVICE_client_continue (client);
411 * Core handler for flooded revocation messages.
413 * @param cls closure unused
414 * @param rm revocation message
417 handle_p2p_revoke (void *cls,
418 const struct RevokeMessage *rm)
420 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
421 "Received REVOKE message\n");
422 GNUNET_break_op (GNUNET_SYSERR !=
428 * Callback for set operation results. Called for each element in the
429 * result set. Each element contains a revocation, which we should
430 * validate and then add to our revocation list (and set).
433 * @param element a result element, only valid if status is #GNUNET_SET_STATUS_OK
434 * @param current_size current set size
435 * @param status see `enum GNUNET_SET_Status`
438 add_revocation (void *cls,
439 const struct GNUNET_SET_Element *element,
440 uint64_t current_size,
441 enum GNUNET_SET_Status status)
443 struct PeerEntry *peer_entry = cls;
444 const struct RevokeMessage *rm;
448 case GNUNET_SET_STATUS_OK:
449 if (element->size != sizeof (struct RevokeMessage))
454 if (GNUNET_BLOCK_TYPE_REVOCATION != element->element_type)
456 GNUNET_STATISTICS_update (stats,
457 gettext_noop ("# unsupported revocations received via set union"),
463 (void) handle_p2p_revoke (NULL,
465 GNUNET_STATISTICS_update (stats,
466 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"),
479 case GNUNET_SET_STATUS_HALF_DONE:
481 case GNUNET_SET_STATUS_DONE:
482 peer_entry->so = NULL;
483 GNUNET_STATISTICS_update (stats,
484 gettext_noop ("# revocation set unions completed"),
496 * The timeout for performing the set union has expired,
497 * run the set operation on the revocation certificates.
502 transmit_task_cb (void *cls)
504 struct PeerEntry *peer_entry = cls;
506 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
507 "Starting set exchange with peer `%s'\n",
508 GNUNET_i2s (&peer_entry->id));
509 peer_entry->transmit_task = NULL;
510 GNUNET_assert (NULL == peer_entry->so);
511 peer_entry->so = GNUNET_SET_prepare (&peer_entry->id,
512 &revocation_set_union_app_id,
514 GNUNET_SET_RESULT_ADDED,
515 (struct GNUNET_SET_Option[]) {{ 0 }},
519 GNUNET_SET_commit (peer_entry->so,
522 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
523 _("SET service crashed, terminating revocation service\n"));
524 GNUNET_SCHEDULER_shutdown ();
531 * Method called whenever a peer connects. Sets up the PeerEntry and
532 * schedules the initial revocation set exchange with this peer.
535 * @param peer peer identity this notification is about
538 handle_core_connect (void *cls,
539 const struct GNUNET_PeerIdentity *peer,
540 struct GNUNET_MQ_Handle *mq)
542 struct PeerEntry *peer_entry;
543 struct GNUNET_HashCode my_hash;
544 struct GNUNET_HashCode peer_hash;
546 if (0 == memcmp (peer,
548 sizeof (my_identity)))
553 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
554 "Peer `%s' connected to us\n",
556 GNUNET_STATISTICS_update (stats,
560 peer_entry = GNUNET_CONTAINER_multipeermap_get (peers,
562 if (NULL != peer_entry)
564 /* This can happen if "core"'s notification is a tad late
565 and CADET+SET were faster and already produced a
566 #handle_revocation_union_request() for us to deal
567 with. This should be rare, but isn't impossible. */
571 peer_entry = new_peer_entry (peer);
573 GNUNET_CRYPTO_hash (&my_identity,
574 sizeof (my_identity),
576 GNUNET_CRYPTO_hash (peer,
579 if (0 < GNUNET_CRYPTO_hash_cmp (&my_hash,
582 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
583 "Starting SET operation with peer `%s'\n",
585 peer_entry->transmit_task =
586 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
595 * Method called whenever a peer disconnects. Deletes the PeerEntry and cancels
596 * any pending transmission requests to that peer.
599 * @param peer peer identity this notification is about
600 * @param internal_cls our `struct PeerEntry` for this peer
603 handle_core_disconnect (void *cls,
604 const struct GNUNET_PeerIdentity *peer,
607 struct PeerEntry *peer_entry = internal_cls;
609 if (0 == memcmp (peer,
611 sizeof (my_identity)))
613 GNUNET_assert (NULL != peer_entry);
614 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
615 "Peer `%s' disconnected from us\n",
617 GNUNET_assert (GNUNET_YES ==
618 GNUNET_CONTAINER_multipeermap_remove (peers,
621 if (NULL != peer_entry->transmit_task)
623 GNUNET_SCHEDULER_cancel (peer_entry->transmit_task);
624 peer_entry->transmit_task = NULL;
626 if (NULL != peer_entry->so)
628 GNUNET_SET_operation_cancel (peer_entry->so);
629 peer_entry->so = NULL;
631 GNUNET_free (peer_entry);
632 GNUNET_STATISTICS_update (stats,
640 * Free all values in a hash map.
644 * @param value value to free
645 * @return #GNUNET_OK (continue to iterate)
648 free_entry (void *cls,
649 const struct GNUNET_HashCode *key,
658 * Task run during shutdown.
663 shutdown_task (void *cls)
665 if (NULL != revocation_set)
667 GNUNET_SET_destroy (revocation_set);
668 revocation_set = NULL;
670 if (NULL != revocation_union_listen_handle)
672 GNUNET_SET_listen_cancel (revocation_union_listen_handle);
673 revocation_union_listen_handle = NULL;
675 if (NULL != core_api)
677 GNUNET_CORE_disconnect (core_api);
682 GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
687 GNUNET_CONTAINER_multipeermap_destroy (peers);
690 if (NULL != revocation_db)
692 GNUNET_DISK_file_close (revocation_db);
693 revocation_db = NULL;
695 GNUNET_CONTAINER_multihashmap_iterate (revocation_map,
698 GNUNET_CONTAINER_multihashmap_destroy (revocation_map);
703 * Called on core init/fail.
705 * @param cls service closure
706 * @param identity the public identity of this peer
709 core_init (void *cls,
710 const struct GNUNET_PeerIdentity *identity)
712 if (NULL == identity)
714 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
715 "Connection to core FAILED!\n");
716 GNUNET_SCHEDULER_shutdown ();
719 my_identity = *identity;
724 * Called when another peer wants to do a set operation with the
725 * local peer. If a listen error occurs, the 'request' is NULL.
728 * @param other_peer the other peer
729 * @param context_msg message with application specific information from
731 * @param request request from the other peer (never NULL), use GNUNET_SET_accept()
732 * to accept it, otherwise the request will be refused
733 * Note that we can't just return value from the listen callback,
734 * as it is also necessary to specify the set we want to do the
735 * operation with, whith sometimes can be derived from the context
736 * message. It's necessary to specify the timeout.
739 handle_revocation_union_request (void *cls,
740 const struct GNUNET_PeerIdentity *other_peer,
741 const struct GNUNET_MessageHeader *context_msg,
742 struct GNUNET_SET_Request *request)
744 struct PeerEntry *peer_entry;
751 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
752 "Received set exchange request from peer `%s'\n",
753 GNUNET_i2s (other_peer));
754 peer_entry = GNUNET_CONTAINER_multipeermap_get (peers,
756 if (NULL == peer_entry)
758 peer_entry = new_peer_entry (other_peer);
760 if (NULL != peer_entry->so)
765 peer_entry->so = GNUNET_SET_accept (request,
766 GNUNET_SET_RESULT_ADDED,
767 (struct GNUNET_SET_Option[]) {{ 0 }},
771 GNUNET_SET_commit (peer_entry->so,
775 GNUNET_SCHEDULER_shutdown ();
782 * Handle network size estimate clients.
785 * @param server the initialized server
786 * @param c configuration to use
790 const struct GNUNET_CONFIGURATION_Handle *c,
791 struct GNUNET_SERVICE_Handle *service)
793 struct GNUNET_MQ_MessageHandler core_handlers[] = {
794 GNUNET_MQ_hd_fixed_size (p2p_revoke,
795 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
796 struct RevokeMessage,
798 GNUNET_MQ_handler_end ()
802 struct RevokeMessage *rm;
803 struct GNUNET_HashCode hc;
805 GNUNET_CRYPTO_hash ("revocation-set-union-application-id",
806 strlen ("revocation-set-union-application-id"),
807 &revocation_set_union_app_id);
809 GNUNET_CONFIGURATION_get_value_filename (c,
814 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
817 GNUNET_SCHEDULER_shutdown ();
821 revocation_map = GNUNET_CONTAINER_multihashmap_create (16,
824 GNUNET_CONFIGURATION_get_value_number (cfg,
827 &revocation_work_required))
829 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
832 GNUNET_SCHEDULER_shutdown ();
836 if (revocation_work_required >= sizeof (struct GNUNET_HashCode) * 8)
838 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
841 _("Value is too large.\n"));
842 GNUNET_SCHEDULER_shutdown ();
846 revocation_set = GNUNET_SET_create (cfg,
847 GNUNET_SET_OPERATION_UNION);
848 revocation_union_listen_handle
849 = GNUNET_SET_listen (cfg,
850 GNUNET_SET_OPERATION_UNION,
851 &revocation_set_union_app_id,
852 &handle_revocation_union_request,
854 revocation_db = GNUNET_DISK_file_open (fn,
855 GNUNET_DISK_OPEN_READWRITE |
856 GNUNET_DISK_OPEN_CREATE,
857 GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE |
858 GNUNET_DISK_PERM_GROUP_READ |
859 GNUNET_DISK_PERM_OTHER_READ);
860 if (NULL == revocation_db)
862 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
865 _("Could not open revocation database file!"));
866 GNUNET_SCHEDULER_shutdown ();
871 GNUNET_DISK_file_size (fn, &left, GNUNET_YES, GNUNET_YES))
873 while (left > sizeof (struct RevokeMessage))
875 rm = GNUNET_new (struct RevokeMessage);
876 if (sizeof (struct RevokeMessage) !=
877 GNUNET_DISK_file_read (revocation_db,
879 sizeof (struct RevokeMessage)))
881 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
885 GNUNET_SCHEDULER_shutdown ();
889 GNUNET_break (0 == ntohl (rm->reserved));
890 GNUNET_CRYPTO_hash (&rm->public_key,
891 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
893 GNUNET_break (GNUNET_OK ==
894 GNUNET_CONTAINER_multihashmap_put (revocation_map,
897 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
901 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
903 peers = GNUNET_CONTAINER_multipeermap_create (128,
905 /* Connect to core service and register core handlers */
906 core_api = GNUNET_CORE_connect (cfg, /* Main configuration */
907 NULL, /* Closure passed to functions */
908 &core_init, /* Call core_init once connected */
909 &handle_core_connect, /* Handle connects */
910 &handle_core_disconnect, /* Handle disconnects */
911 core_handlers); /* Register these handlers */
912 if (NULL == core_api)
914 GNUNET_SCHEDULER_shutdown ();
917 stats = GNUNET_STATISTICS_create ("revocation",
923 * Define "main" method using service macro.
927 GNUNET_SERVICE_OPTION_NONE,
930 &client_disconnect_cb,
932 GNUNET_MQ_hd_fixed_size (query_message,
933 GNUNET_MESSAGE_TYPE_REVOCATION_QUERY,
936 GNUNET_MQ_hd_fixed_size (revoke_message,
937 GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
938 struct RevokeMessage,
940 GNUNET_MQ_handler_end ());
943 #if defined(LINUX) && defined(__GLIBC__)
947 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
949 void __attribute__ ((constructor))
950 GNUNET_REVOCATION_memory_init ()
952 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
953 mallopt (M_TOP_PAD, 1 * 1024);
960 /* end of gnunet-service-revocation.c */