anastasis-gtk

Demonstrator GUI for Anastasis
Log | Files | Refs | README | LICENSE

anastasis-gtk_handle-method-totp.c (9712B)


      1 /*
      2      This file is part of anastasis-gtk.
      3      Copyright (C) 2021 Anastasis SARL
      4 
      5      Anastasis is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      Anastasis 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 License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with Anastasis; 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.
     19 */
     20 
     21 /**
     22  * @file src/anastasis/anastasis-gtk_handle-method-totp.c
     23  * @brief Handle dialogs for TOTP (RFC 6238)
     24  * @author Christian Grothoff
     25  */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include "anastasis_gtk_util.h"
     28 #include "anastasis-gtk_action.h"
     29 #include "anastasis-gtk_helper.h"
     30 #include "anastasis-gtk_handle-identity-changed.h"
     31 #include <jansson.h>
     32 #include <gcrypt.h>
     33 
     34 /**
     35  * How long is a TOTP code valid?
     36  */
     37 #define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \
     38           GNUNET_TIME_UNIT_SECONDS, 30)
     39 
     40 /**
     41  * Range of time we allow (plus-minus).
     42  */
     43 #define TIME_INTERVAL_RANGE 2
     44 
     45 /**
     46  * How long is the shared secret in bytes?
     47  */
     48 #define SECRET_LEN 32
     49 
     50 /**
     51  * Random secret used in the current dialog.
     52  */
     53 static char totp_key[SECRET_LEN];
     54 
     55 
     56 /**
     57  * Compute TOTP code at current time with offset
     58  * @a time_off for the @a key.
     59  *
     60  * @param time_off offset to apply when computing the code
     61  * @return TOTP code at this time
     62  */
     63 static uint64_t
     64 compute_totp (int time_off)
     65 {
     66   struct GNUNET_TIME_Absolute now;
     67   time_t t;
     68   uint64_t ctr;
     69   uint8_t hmac[20]; /* SHA1: 20 bytes */
     70 
     71   now = GNUNET_TIME_absolute_get ();
     72   while (time_off < 0)
     73   {
     74     now = GNUNET_TIME_absolute_subtract (now,
     75                                          TOTP_VALIDITY_PERIOD);
     76     time_off++;
     77   }
     78   while (time_off > 0)
     79   {
     80     now = GNUNET_TIME_absolute_add (now,
     81                                     TOTP_VALIDITY_PERIOD);
     82     time_off--;
     83   }
     84   t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
     85   ctr = GNUNET_htonll (t / 30LLU);
     86 
     87   {
     88     gcry_md_hd_t md;
     89     const unsigned char *mc;
     90 
     91     GNUNET_assert (GPG_ERR_NO_ERROR ==
     92                    gcry_md_open (&md,
     93                                  GCRY_MD_SHA1,
     94                                  GCRY_MD_FLAG_HMAC));
     95     GNUNET_assert (GPG_ERR_NO_ERROR ==
     96                    gcry_md_setkey (md,
     97                                    totp_key,
     98                                    sizeof (totp_key)));
     99     gcry_md_write (md,
    100                    &ctr,
    101                    sizeof (ctr));
    102     mc = gcry_md_read (md,
    103                        GCRY_MD_SHA1);
    104     GNUNET_assert (NULL != mc);
    105     memcpy (hmac,
    106             mc,
    107             sizeof (hmac));
    108     gcry_md_close (md);
    109   }
    110 
    111   {
    112     uint32_t code = 0;
    113     int offset;
    114 
    115     offset = hmac[sizeof (hmac) - 1] & 0x0f;
    116     for (int count = 0; count < 4; count++)
    117       code |= hmac[offset + 3 - count] << (8 * count);
    118     code &= 0x7fffffff;
    119     /* always use 8 digits (maximum) */
    120     code = code % 100000000;
    121     return code;
    122   }
    123 }
    124 
    125 
    126 /**
    127  * Compute RFC 3548 base32 encoding of @a val and write
    128  * result to @a enc.
    129  *
    130  * @param val value to encode
    131  * @param val_size number of bytes in @a val
    132  * @param[out] enc where to write the 0-terminated result
    133  */
    134 static void
    135 base32enc (const void *val,
    136            size_t val_size,
    137            char *enc)
    138 {
    139   /**
    140    * 32 characters for encoding, using RFC 3548.
    141    */
    142   static const char *encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    143   unsigned int wpos;
    144   unsigned int rpos;
    145   unsigned int bits;
    146   unsigned int vbit;
    147   const unsigned char *udata;
    148 
    149   udata = val;
    150   vbit = 0;
    151   wpos = 0;
    152   rpos = 0;
    153   bits = 0;
    154   while ((rpos < val_size) || (vbit > 0))
    155   {
    156     if ((rpos < val_size) && (vbit < 5))
    157     {
    158       bits = (bits << 8) | udata[rpos++];     /* eat 8 more bits */
    159       vbit += 8;
    160     }
    161     if (vbit < 5)
    162     {
    163       bits <<= (5 - vbit);     /* zero-padding */
    164       GNUNET_assert (vbit == ((val_size * 8) % 5));
    165       vbit = 5;
    166     }
    167     enc[wpos++] = encTable__[(bits >> (vbit - 5)) & 31];
    168     vbit -= 5;
    169   }
    170   GNUNET_assert (0 == vbit);
    171   enc[wpos] = '\0';
    172 }
    173 
    174 
    175 /**
    176  * Recompute the QR code shown in @a builder from
    177  * totp and the user's name for the secret.
    178  *
    179  * @param builder the dialog builder
    180  */
    181 static void
    182 refresh_totp (GtkBuilder *builder)
    183 {
    184   GtkEntry *q;
    185   const char *name;
    186   char *u_name;
    187   char *uri;
    188   char base_sec[sizeof (totp_key) * 2];
    189   GdkPixbuf *pb;
    190   GtkImage *img;
    191 
    192   gtk_widget_set_sensitive (
    193     GTK_WIDGET (gtk_builder_get_object (builder,
    194                                         "anastasis_gtk_b_totp_dialog_btn_ok")),
    195     FALSE);
    196   q = GTK_ENTRY (gtk_builder_get_object (builder,
    197                                          "anastasis_gtk_b_totp_dialog_name_entry"));
    198   name = gtk_entry_get_text (q);
    199   u_name = TALER_urlencode (name);
    200   base32enc (totp_key,
    201              sizeof (totp_key),
    202              base_sec);
    203   GNUNET_asprintf (&uri,
    204                    "otpauth://totp/%s?digits=8&secret=%s",
    205                    u_name,
    206                    base_sec);
    207   GNUNET_free (u_name);
    208   img = GTK_IMAGE (gtk_builder_get_object (builder,
    209                                            "qr_image"));
    210   pb = AG_setup_qrcode (GTK_WIDGET (img),
    211                         uri,
    212                         strlen (uri));
    213   GNUNET_free (uri);
    214   if (NULL != pb)
    215   {
    216     gtk_image_set_from_pixbuf (img,
    217                                pb);
    218     g_object_unref (pb);
    219   }
    220 }
    221 
    222 
    223 /**
    224  * Function called from the totp dialog upon completion.
    225  *
    226  * @param dialog the pseudonym selection dialog
    227  * @param response_id response code from the dialog
    228  * @param user_data the builder of the dialog
    229  */
    230 void
    231 anastasis_gtk_b_totp_dialog_response_cb (GtkDialog *dialog,
    232                                          gint response_id,
    233                                          gpointer user_data)
    234 {
    235   GtkBuilder *builder = GTK_BUILDER (user_data);
    236   GtkEntry *q;
    237   const char *name;
    238   json_t *args;
    239 
    240   if (GTK_RESPONSE_OK != response_id)
    241   {
    242     gtk_widget_destroy (GTK_WIDGET (dialog));
    243     g_object_unref (G_OBJECT (builder));
    244     return;
    245   }
    246   q = GTK_ENTRY (gtk_builder_get_object (builder,
    247                                          "anastasis_gtk_b_totp_dialog_name_entry"));
    248   name = gtk_entry_get_text (q);
    249   args = json_pack ("{ s:{s:s, s:o, s:s}}",
    250                     "authentication_method",
    251                     "type",
    252                     "totp",
    253                     "challenge",
    254                     GNUNET_JSON_from_data (totp_key,
    255                                            sizeof (totp_key)),
    256                     "instructions",
    257                     name);
    258   gtk_widget_destroy (GTK_WIDGET (dialog));
    259   g_object_unref (G_OBJECT (builder));
    260   memset (totp_key,
    261           0,
    262           sizeof (totp_key));
    263   AG_freeze ();
    264   AG_ra = ANASTASIS_redux_action (AG_redux_state,
    265                                   "add_authentication",
    266                                   args,
    267                                   &AG_action_cb,
    268                                   NULL);
    269   json_decref (args);
    270 }
    271 
    272 
    273 void
    274 totp_entry_changed_cb (GtkEntry *entry,
    275                        gpointer user_data)
    276 {
    277   GtkBuilder *builder = GTK_BUILDER (user_data);
    278   GtkWidget *but;
    279   GtkEntry *q;
    280   const char *code;
    281   unsigned int val;
    282   char dummy;
    283   bool found = false;
    284 
    285   q = GTK_ENTRY (gtk_builder_get_object (builder,
    286                                          "totp_entry"));
    287   code = gtk_entry_get_text (q);
    288   if (1 != sscanf (code,
    289                    "%u%c",
    290                    &val,
    291                    &dummy))
    292     return;
    293   for (int i = -TIME_INTERVAL_RANGE;
    294        i <= TIME_INTERVAL_RANGE;
    295        i++)
    296   {
    297     if (val == compute_totp (i))
    298     {
    299       found = true;
    300       break;
    301     }
    302   }
    303   if (! found)
    304     return;
    305   but = GTK_WIDGET (gtk_builder_get_object (builder,
    306                                             "anastasis_gtk_b_totp_dialog_btn_ok"));
    307   gtk_widget_set_sensitive (but,
    308                             TRUE);
    309 }
    310 
    311 
    312 void
    313 anastasis_gtk_b_totp_dialog_name_entry_changed_cb (GtkEntry *entry,
    314                                                    gpointer user_data)
    315 {
    316   GtkBuilder *builder = GTK_BUILDER (user_data);
    317   GtkEntry *e;
    318 
    319   /* clear code user already entered, if any */
    320   e = GTK_ENTRY (gtk_builder_get_object (builder,
    321                                          "totp_entry"));
    322   gtk_entry_set_text (e, "");
    323   refresh_totp (builder);
    324 }
    325 
    326 
    327 /**
    328  * Callback invoked if the the "totp"-button is clicked.
    329  *
    330  * @param object
    331  * @param user_data unused
    332  */
    333 void
    334 anastasis_gtk_btn_add_auth_totp_clicked_cb (GObject *object,
    335                                             gpointer user_data)
    336 {
    337   GtkWidget *ad;
    338   GtkBuilder *builder;
    339 
    340   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    341                               totp_key,
    342                               sizeof (totp_key));
    343   builder = ANASTASIS_GTK_get_new_builder (
    344     ANASTASIS_GTK_project_data (),
    345     "anastasis_gtk_auth_add_totp.glade",
    346     NULL);
    347   if (NULL == builder)
    348   {
    349     GNUNET_break (0);
    350     return;
    351   }
    352   ad = GTK_WIDGET (gtk_builder_get_object (builder,
    353                                            "anastasis_gtk_b_totp_dialog"));
    354   refresh_totp (builder);
    355   {
    356     GtkWidget *toplevel;
    357 
    358     toplevel = gtk_widget_get_toplevel (GTK_WIDGET (object));
    359     gtk_window_set_transient_for (GTK_WINDOW (ad),
    360                                   GTK_WINDOW (toplevel));
    361     gtk_window_present (GTK_WINDOW (ad));
    362   }
    363 }