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 }