16c3640da1d9523a59b71e4abc7f82ce5bb5b9b8
[oweals/gnunet.git] / src / revocation / gnunet-service-revocation.c
1 /*
2   This file is part of GNUnet.
3   (C) 2013 Christian Grothoff (and other contributing authors)
4
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.
9
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.
14
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., 59 Temple Place - Suite 330,
18   Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * @file revocation/gnunet-service-revocation.c
23  * @brief key revocation service
24  * @author Christian Grothoff
25  *
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
31  * peers that connect.
32  *
33  * TODO:
34  * - broadcast p2p revocations
35  * - handle p2p connect (trigger SET union)
36  * - optimization: avoid sending revocation back to peer that we got it from;
37  * - optimization: have randomized delay in sending revocations to other peers
38  *                 to make it rare to traverse each link twice (NSE-style)
39  */
40 #include "platform.h"
41 #include <math.h>
42 #include "gnunet_util_lib.h"
43 #include "gnunet_constants.h"
44 #include "gnunet_protocols.h"
45 #include "gnunet_signatures.h"
46 #include "gnunet_statistics_service.h"
47 #include "gnunet_core_service.h"
48 #include "gnunet_revocation_service.h"
49 #include "gnunet_set_service.h"
50 #include "revocation.h"
51 #include <gcrypt.h>
52
53
54 /**
55  * Per-peer information.
56  */
57 struct PeerEntry
58 {
59
60   /**
61    * Core handle for sending messages to this peer.
62    */
63   struct GNUNET_CORE_TransmitHandle *th;
64
65   /**
66    * What is the identity of the peer?
67    */
68   struct GNUNET_PeerIdentity id;
69
70   /**
71    * Task scheduled to send message to this peer.
72    */
73   GNUNET_SCHEDULER_TaskIdentifier transmit_task;
74
75 };
76
77
78 /**
79  * Set from all revocations known to us.
80  */
81 static struct GNUNET_SET_Handle *revocation_set;
82
83 /**
84  * Hash map with all revoked keys, maps the hash of the public key
85  * to the respective `struct RevokeMessage`.
86  */
87 static struct GNUNET_CONTAINER_MultiHashMap *revocation_map;
88
89 /**
90  * Handle to our current configuration.
91  */
92 static const struct GNUNET_CONFIGURATION_Handle *cfg;
93
94 /**
95  * Handle to the statistics service.
96  */
97 static struct GNUNET_STATISTICS_Handle *stats;
98
99 /**
100  * Handle to the core service (for flooding)
101  */
102 static struct GNUNET_CORE_Handle *coreAPI;
103
104 /**
105  * Map of all connected peers.
106  */
107 static struct GNUNET_CONTAINER_MultiPeerMap *peers;
108
109 /**
110  * The peer identity of this peer.
111  */
112 static struct GNUNET_PeerIdentity my_identity;
113
114 /**
115  * Handle to this serivce's server.
116  */
117 static struct GNUNET_SERVER_Handle *srv;
118
119 /**
120  * Notification context for convenient sending of replies to the clients.
121  */
122 static struct GNUNET_SERVER_NotificationContext *nc;
123
124 /**
125  * File handle for the revocation database.
126  */
127 static struct GNUNET_DISK_FileHandle *revocation_db;
128
129 /**
130  * Amount of work required (W-bit collisions) for REVOCATION proofs, in collision-bits.
131  */
132 static unsigned long long revocation_work_required;
133
134
135 /**
136  * An revoke message has been received, check that it is well-formed.
137  *
138  * @param rm the message to verify
139  * @return #GNUNET_YES if the message is verified
140  *         #GNUNET_NO if the key/signature don't verify
141  */
142 static int
143 verify_revoke_message (const struct RevokeMessage *rm)
144 {
145   if (GNUNET_YES !=
146       GNUNET_REVOCATION_check_pow (&rm->public_key,
147                                    rm->proof_of_work,
148                                    (unsigned int) revocation_work_required))
149   {
150     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
151                 "Proof of work invalid!\n");
152     GNUNET_break_op (0);
153     return GNUNET_NO;
154   }
155   if (GNUNET_OK !=
156       GNUNET_CRYPTO_ecc_verify (GNUNET_SIGNATURE_PURPOSE_REVOCATION,
157                                 &rm->purpose,
158                                 &rm->signature,
159                                 &rm->public_key))
160   {
161     GNUNET_break_op (0);
162     return GNUNET_NO;
163   }
164   return GNUNET_YES;
165 }
166
167
168 /**
169  * Handle QUERY message from client.
170  *
171  * @param cls unused
172  * @param client who sent the message
173  * @param message the message received
174  */
175 static void
176 handle_query_message (void *cls,
177                       struct GNUNET_SERVER_Client *client,
178                       const struct GNUNET_MessageHeader *message)
179 {
180   const struct QueryMessage *qm = (const struct QueryMessage *) message;
181   struct QueryResponseMessage qrm;
182   struct GNUNET_HashCode hc;
183   int res;
184
185   GNUNET_CRYPTO_hash (&qm->key,
186                       sizeof (struct GNUNET_CRYPTO_EccPublicSignKey),
187                       &hc);
188   res = GNUNET_CONTAINER_multihashmap_contains (revocation_map, &hc);
189   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
190               (GNUNET_NO == res)
191               ? "Received revocation check for valid key `%s' from client\n"
192               : "Received revocation check for revoked key `%s' from client\n",
193               GNUNET_h2s (&hc));
194   qrm.header.size = htons (sizeof (struct QueryResponseMessage));
195   qrm.header.type = htons (GNUNET_MESSAGE_TYPE_REVOCATION_QUERY_RESPONSE);
196   qrm.is_valid = htonl ((GNUNET_YES == res) ? GNUNET_NO : GNUNET_YES);
197   GNUNET_SERVER_notification_context_add (nc,
198                                           client);
199   GNUNET_SERVER_notification_context_unicast (nc,
200                                               client,
201                                               &qrm.header,
202                                               GNUNET_NO);
203   GNUNET_SERVER_receive_done (client, GNUNET_OK);
204 }
205
206
207 /**
208  * Flood the given revocation message to all neighbours.
209  *
210  * @param cls the `struct RevokeMessage` to flood
211  * @param target a neighbour
212  * @param value our `struct PeerEntry` for the neighbour
213  * @return #GNUNET_OK (continue to iterate)
214  */
215 static int
216 do_flood (void *cls,
217           const struct GNUNET_PeerIdentity *target,
218           void *value)
219 {
220   GNUNET_break (0); // FIXME: not implemented
221   return GNUNET_OK;
222 }
223
224
225 /**
226  * Publicize revocation message.   Stores the message locally in the
227  * database and passes it to all connected neighbours (and adds it to
228  * the set for future connections).
229  *
230  * @param rm message to publicize
231  * @return #GNUNET_OK on success, #GNUNET_NO if we encountered an error,
232  *         #GNUNET_SYSERR if the message was malformed
233  */
234 static int
235 publicize_rm (const struct RevokeMessage *rm)
236 {
237   struct RevokeMessage *cp;
238   struct GNUNET_HashCode hc;
239   struct GNUNET_SET_Element e;
240
241   GNUNET_CRYPTO_hash (&rm->public_key,
242                       sizeof (struct GNUNET_CRYPTO_EccPublicSignKey),
243                       &hc);
244   if (GNUNET_YES ==
245       GNUNET_CONTAINER_multihashmap_contains (revocation_map,
246                                               &hc))
247   {
248     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
249                 _("Duplicate revocation received from peer. Ignored.\n"));
250     return GNUNET_OK;
251   }
252   if (GNUNET_OK !=
253       verify_revoke_message (rm))
254   {
255     GNUNET_break_op (0);
256     return GNUNET_SYSERR;
257   }
258   /* write to disk */
259   if (sizeof (struct RevokeMessage) !=
260       GNUNET_DISK_file_write (revocation_db,
261                               rm,
262                               sizeof (struct RevokeMessage)))
263   {
264     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
265                          "write");
266     return GNUNET_NO;
267   }
268   if (GNUNET_OK !=
269       GNUNET_DISK_file_sync (revocation_db))
270   {
271     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
272                          "sync");
273     return GNUNET_NO;
274   }
275   /* keep copy in memory */
276   cp = (struct RevokeMessage *) GNUNET_copy_message (&rm->header);
277   GNUNET_break (GNUNET_OK ==
278                 GNUNET_CONTAINER_multihashmap_put (revocation_map,
279                                                    &hc,
280                                                    cp,
281                                                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
282   /* add to set for future connections */
283   e.size = htons (rm->header.size);
284   e.type = 0;
285   e.data = rm;
286   if (GNUNET_OK !=
287       GNUNET_SET_add_element (revocation_set,
288                               &e,
289                               NULL, NULL))
290   {
291     GNUNET_break (0);
292     return GNUNET_OK;
293   }
294   /* flood to neighbours */
295   GNUNET_CONTAINER_multipeermap_iterate (peers,
296                                          &do_flood,
297                                          cp);
298   return GNUNET_OK;
299 }
300
301
302 /**
303  * Handle REVOKE message from client.
304  *
305  * @param cls unused
306  * @param client who sent the message
307  * @param message the message received
308  */
309 static void
310 handle_revoke_message (void *cls,
311                        struct GNUNET_SERVER_Client *client,
312                        const struct GNUNET_MessageHeader *message)
313 {
314   const struct RevokeMessage *rm;
315   struct RevocationResponseMessage rrm;
316   int ret;
317
318   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
319               "Received REVOKE message from client\n");
320   rm = (const struct RevokeMessage *) message;
321   if (GNUNET_SYSERR == (ret = publicize_rm (rm)))
322   {
323     GNUNET_break_op (0);
324     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
325     return;
326   }
327   rrm.header.size = htons (sizeof (struct RevocationResponseMessage));
328   rrm.header.type = htons (GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE_RESPONSE);
329   rrm.is_valid = htonl ((GNUNET_OK == ret) ? GNUNET_NO : GNUNET_YES);
330   GNUNET_SERVER_notification_context_add (nc,
331                                           client);
332   GNUNET_SERVER_notification_context_unicast (nc,
333                                               client,
334                                               &rrm.header,
335                                               GNUNET_NO);
336   GNUNET_SERVER_receive_done (client, GNUNET_OK);
337 }
338
339
340 /**
341  * Core handler for flooded revocation messages.
342  *
343  * @param cls closure unused
344  * @param message message
345  * @param peer peer identity this message is from (ignored)
346  */
347 static int
348 handle_p2p_revoke_message (void *cls,
349                            const struct GNUNET_PeerIdentity *peer,
350                            const struct GNUNET_MessageHeader *message)
351 {
352   const struct RevokeMessage *rm;
353
354   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
355               "Received REVOKE message from peer\n");
356   rm = (const struct RevokeMessage *) message;
357   GNUNET_break_op (GNUNET_SYSERR != publicize_rm (rm));
358   return GNUNET_OK;
359 }
360
361
362 /**
363  * Method called whenever a peer connects. Sets up the PeerEntry and
364  * schedules the initial revocation set exchange with this peer.
365  *
366  * @param cls closure
367  * @param peer peer identity this notification is about
368  */
369 static void
370 handle_core_connect (void *cls,
371                      const struct GNUNET_PeerIdentity *peer)
372 {
373   struct PeerEntry *peer_entry;
374
375   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Peer `%s' connected to us\n",
376               GNUNET_i2s (peer));
377   peer_entry = GNUNET_new (struct PeerEntry);
378   peer_entry->id = *peer;
379   GNUNET_assert (GNUNET_OK ==
380                  GNUNET_CONTAINER_multipeermap_put (peers, peer,
381                                                     peer_entry,
382                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
383   GNUNET_break (0); // FIXME: implement revocation set union on connect!
384 #if 0
385   peer_entry->transmit_task =
386       GNUNET_SCHEDULER_add_delayed (get_transmit_delay (-1), &transmit_task_cb,
387                                     peer_entry);
388 #endif
389   GNUNET_STATISTICS_update (stats, "# peers connected", 1, GNUNET_NO);
390 }
391
392
393 /**
394  * Method called whenever a peer disconnects. Deletes the PeerEntry and cancels
395  * any pending transmission requests to that peer.
396  *
397  * @param cls closure
398  * @param peer peer identity this notification is about
399  */
400 static void
401 handle_core_disconnect (void *cls,
402                         const struct GNUNET_PeerIdentity *peer)
403 {
404   struct PeerEntry *pos;
405
406   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
407               "Peer `%s' disconnected from us\n",
408               GNUNET_i2s (peer));
409   pos = GNUNET_CONTAINER_multipeermap_get (peers, peer);
410   if (NULL == pos)
411   {
412     GNUNET_break (0);
413     return;
414   }
415   GNUNET_assert (GNUNET_YES ==
416                  GNUNET_CONTAINER_multipeermap_remove (peers, peer,
417                                                        pos));
418 #if 0
419   if (pos->transmit_task != GNUNET_SCHEDULER_NO_TASK)
420   {
421     GNUNET_SCHEDULER_cancel (pos->transmit_task);
422     pos->transmit_task = GNUNET_SCHEDULER_NO_TASK;
423   }
424   if (NULL != pos->th)
425   {
426     GNUNET_CORE_notify_transmit_ready_cancel (pos->th);
427     pos->th = NULL;
428   }
429 #endif
430   GNUNET_free (pos);
431   GNUNET_STATISTICS_update (stats, "# peers connected", -1, GNUNET_NO);
432 }
433
434
435 /**
436  * Free all values in a hash map.
437  *
438  * @param cls NULL
439  * @param key the key
440  * @param value value to free
441  * @return #GNUNET_OK (continue to iterate)
442  */
443 static int
444 free_entry (void *cls,
445             const struct GNUNET_HashCode *key,
446             void *value)
447 {
448   GNUNET_free (value);
449   return GNUNET_OK;
450 }
451
452
453 /**
454  * Task run during shutdown.
455  *
456  * @param cls unused
457  * @param tc unused
458  */
459 static void
460 shutdown_task (void *cls,
461                const struct GNUNET_SCHEDULER_TaskContext *tc)
462 {
463   if (NULL != revocation_set)
464   {
465     GNUNET_SET_destroy (revocation_set);
466     revocation_set = NULL;
467   }
468   if (NULL != coreAPI)
469   {
470     GNUNET_CORE_disconnect (coreAPI);
471     coreAPI = NULL;
472   }
473   if (NULL != stats)
474   {
475     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
476     stats = NULL;
477   }
478   if (NULL != peers)
479   {
480     GNUNET_CONTAINER_multipeermap_destroy (peers);
481     peers = NULL;
482   }
483   if (NULL != nc)
484   {
485     GNUNET_SERVER_notification_context_destroy (nc);
486     nc = NULL;
487   }
488   if (NULL != revocation_db)
489   {
490     GNUNET_DISK_file_close (revocation_db);
491     revocation_db = NULL;
492   }
493   GNUNET_CONTAINER_multihashmap_iterate (revocation_map,
494                                          &free_entry,
495                                          NULL);
496   GNUNET_CONTAINER_multihashmap_destroy (revocation_map);
497 }
498
499
500 /**
501  * Called on core init/fail.
502  *
503  * @param cls service closure
504  * @param identity the public identity of this peer
505  */
506 static void
507 core_init (void *cls,
508            const struct GNUNET_PeerIdentity *identity)
509 {
510   if (NULL == identity)
511   {
512     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
513                 "Connection to core FAILED!\n");
514     GNUNET_SCHEDULER_shutdown ();
515     return;
516   }
517   my_identity = *identity;
518 }
519
520
521 /**
522  * Handle network size estimate clients.
523  *
524  * @param cls closure
525  * @param server the initialized server
526  * @param c configuration to use
527  */
528 static void
529 run (void *cls,
530      struct GNUNET_SERVER_Handle *server,
531      const struct GNUNET_CONFIGURATION_Handle *c)
532 {
533   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
534     {&handle_query_message, NULL, GNUNET_MESSAGE_TYPE_REVOCATION_QUERY,
535      sizeof (struct QueryMessage)},
536     {&handle_revoke_message, NULL, GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
537      sizeof (struct RevokeMessage)},
538     {NULL, NULL, 0, 0}
539   };
540   static const struct GNUNET_CORE_MessageHandler core_handlers[] = {
541     {&handle_p2p_revoke_message, GNUNET_MESSAGE_TYPE_REVOCATION_REVOKE,
542      sizeof (struct RevokeMessage)},
543     {NULL, 0, 0}
544   };
545   char *fn;
546   uint64_t left;
547   struct RevokeMessage *rm;
548   struct GNUNET_HashCode hc;
549
550   if (GNUNET_OK !=
551       GNUNET_CONFIGURATION_get_value_filename (c,
552                                                "REVOCATION",
553                                                "DATABASE",
554                                                &fn))
555   {
556     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
557                                "REVOCATION",
558                                "DATABASE");
559     GNUNET_SCHEDULER_shutdown ();
560     return;
561   }
562   cfg = c;
563   srv = server;
564   revocation_map = GNUNET_CONTAINER_multihashmap_create (16, GNUNET_NO);
565   nc = GNUNET_SERVER_notification_context_create (server, 1);
566   if (GNUNET_OK !=
567       GNUNET_CONFIGURATION_get_value_number (cfg, "REVOCATION", "WORKBITS",
568                                              &revocation_work_required))
569   {
570     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
571                                "REVOCATION",
572                                "WORKBITS");
573     GNUNET_SCHEDULER_shutdown ();
574     GNUNET_free (fn);
575     return;
576   }
577   if (revocation_work_required >= sizeof (struct GNUNET_HashCode) * 8)
578   {
579     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
580                                "REVOCATION",
581                                "WORKBITS",
582                                _("Value is too large.\n"));
583     GNUNET_SCHEDULER_shutdown ();
584     GNUNET_free (fn);
585     return;
586   }
587   revocation_set = GNUNET_SET_create (cfg,
588                                       GNUNET_SET_OPERATION_UNION);
589
590   revocation_db = GNUNET_DISK_file_open (fn,
591                                          GNUNET_DISK_OPEN_READWRITE |
592                                          GNUNET_DISK_OPEN_CREATE,
593                                          GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE |
594                                          GNUNET_DISK_PERM_GROUP_READ |
595                                          GNUNET_DISK_PERM_OTHER_READ);
596   if (NULL == revocation_db)
597   {
598     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
599                                "REVOCATION",
600                                "DATABASE",
601                                _("Could not open revocation database file!"));
602     GNUNET_SCHEDULER_shutdown ();
603     GNUNET_free (fn);
604     return;
605   }
606   if (GNUNET_OK !=
607       GNUNET_DISK_file_size (fn, &left, GNUNET_YES, GNUNET_YES))
608     left = 0;
609   while (left > sizeof (struct RevokeMessage))
610   {
611     rm = GNUNET_new (struct RevokeMessage);
612     if (sizeof (struct RevokeMessage) !=
613         GNUNET_DISK_file_read (revocation_db,
614                                rm,
615                                sizeof (struct RevokeMessage)))
616     {
617       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
618                                 "read",
619                                 fn);
620       GNUNET_free (rm);
621       GNUNET_SCHEDULER_shutdown ();
622       GNUNET_free (fn);
623       return;
624     }
625     GNUNET_break (0 == ntohl (rm->reserved));
626     GNUNET_CRYPTO_hash (&rm->public_key,
627                         sizeof (struct GNUNET_CRYPTO_EccPublicSignKey),
628                         &hc);
629     GNUNET_break (GNUNET_OK ==
630                   GNUNET_CONTAINER_multihashmap_put (revocation_map,
631                                                      &hc,
632                                                      rm,
633                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
634   }
635   GNUNET_free (fn);
636
637   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
638                                 NULL);
639   peers = GNUNET_CONTAINER_multipeermap_create (128, GNUNET_NO);
640   GNUNET_SERVER_add_handlers (srv, handlers);
641    /* Connect to core service and register core handlers */
642   coreAPI = GNUNET_CORE_connect (cfg,   /* Main configuration */
643                                  NULL,       /* Closure passed to functions */
644                                  &core_init,    /* Call core_init once connected */
645                                  &handle_core_connect,  /* Handle connects */
646                                  &handle_core_disconnect,       /* Handle disconnects */
647                                  NULL,  /* Don't want notified about all incoming messages */
648                                  GNUNET_NO,     /* For header only inbound notification */
649                                  NULL,  /* Don't want notified about all outbound messages */
650                                  GNUNET_NO,     /* For header only outbound notification */
651                                  core_handlers);        /* Register these handlers */
652   if (NULL == coreAPI)
653   {
654     GNUNET_SCHEDULER_shutdown ();
655     return;
656   }
657   stats = GNUNET_STATISTICS_create ("revocation", cfg);
658 }
659
660
661 /**
662  * The main function for the network size estimation service.
663  *
664  * @param argc number of arguments from the command line
665  * @param argv command line arguments
666  * @return 0 ok, 1 on error
667  */
668 int
669 main (int argc,
670       char *const *argv)
671 {
672   return (GNUNET_OK ==
673           GNUNET_SERVICE_run (argc, argv, "revocation", GNUNET_SERVICE_OPTION_NONE,
674                               &run, NULL)) ? 0 : 1;
675 }
676
677
678 #ifdef LINUX
679 #include <malloc.h>
680
681 /**
682  * MINIMIZE heap size (way below 128k) since this process doesn't need much.
683  */
684 void __attribute__ ((constructor))
685 GNUNET_ARM_memory_init ()
686 {
687   mallopt (M_TRIM_THRESHOLD, 4 * 1024);
688   mallopt (M_TOP_PAD, 1 * 1024);
689   malloc_trim (0);
690 }
691 #endif
692
693
694
695 /* end of gnunet-service-revocation.c */