diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-09-26 15:36:34 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-09-26 15:36:34 +0200 |
commit | c07ae7d8d5f39a94b3792c9267d8463746f79ba8 (patch) | |
tree | c8be56869f85f2581e75fbf8f9a5f527537355f2 /src/anastasis | |
parent | c03840e32b6ac72497d317c3646304b30a9e9793 (diff) | |
download | anastasis-gtk-c07ae7d8d5f39a94b3792c9267d8463746f79ba8.tar.gz anastasis-gtk-c07ae7d8d5f39a94b3792c9267d8463746f79ba8.tar.bz2 anastasis-gtk-c07ae7d8d5f39a94b3792c9267d8463746f79ba8.zip |
preliminary totp support for anastasis-gtk
Diffstat (limited to 'src/anastasis')
-rw-r--r-- | src/anastasis/Makefile.am | 1 | ||||
-rw-r--r-- | src/anastasis/anastasis-gtk_action.c | 197 | ||||
-rw-r--r-- | src/anastasis/anastasis-gtk_handle-method-totp.c | 235 | ||||
-rw-r--r-- | src/anastasis/anastasis-gtk_helper.c | 133 | ||||
-rw-r--r-- | src/anastasis/anastasis-gtk_helper.h | 15 |
5 files changed, 424 insertions, 157 deletions
diff --git a/src/anastasis/Makefile.am b/src/anastasis/Makefile.am index b137fc1..aabc843 100644 --- a/src/anastasis/Makefile.am +++ b/src/anastasis/Makefile.am @@ -44,6 +44,7 @@ anastasis_gtk_SOURCES = \ anastasis-gtk_handle-method-post.c \ anastasis-gtk_handle-method-question.c \ anastasis-gtk_handle-method-sms.c \ + anastasis-gtk_handle-method-totp.c \ anastasis-gtk_handle-payqr-selection-changed.c \ anastasis-gtk_handle-policy-activate.c \ anastasis-gtk_handle-policy-button.c \ diff --git a/src/anastasis/anastasis-gtk_action.c b/src/anastasis/anastasis-gtk_action.c index b3c5333..5d4b461 100644 --- a/src/anastasis/anastasis-gtk_action.c +++ b/src/anastasis/anastasis-gtk_action.c @@ -1334,143 +1334,6 @@ action_secret_editing (void) } -/** - * Create the QR code image for our zone. - * - * @param scale factor for scaling up the size of the image to create - * @param text text to encode - * @return NULL on error - */ -static GdkPixbuf * -create_qrcode (unsigned int scale, - const char *text, - size_t text_size) -{ - QRinput *qri; - QRcode *qrc; - GdkPixbuf *pb; - guchar *pixels; - int n_channels; - const char *dir; - char *fn; - unsigned int size; - - qri = QRinput_new2 (0, QR_ECLEVEL_M); - if (NULL == qri) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2"); - return NULL; - } - /* first try encoding as uppercase-only alpha-numerical - QR code (much smaller encoding); if that fails, also - try using binary encoding (in case nick contains - special characters). */ - if ((0 != QRinput_append (qri, - QR_MODE_AN, - text_size, - (unsigned char *) text)) && - (0 != QRinput_append (qri, - QR_MODE_8, - text_size, - (unsigned char *) text))) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "QRinput_append"); - return NULL; - } - qrc = QRcode_encodeInput (qri); - if (NULL == qrc) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "QRcode_encodeInput"); - QRinput_free (qri); - return NULL; - } - /* We use a trick to create a pixbuf in a way that works for both Gtk2 and - Gtk3 by loading a dummy file from disk; all other methods are not portable - to both Gtk2 and Gtk3. */ - dir = GNUNET_GTK_get_data_dir (); - GNUNET_asprintf (&fn, - "%s%s", - dir, - "qr_dummy.png"); - size = (qrc->width + 8) * scale; - size += 8 - (size % 8); - pb = gdk_pixbuf_new_from_file_at_size (fn, - size, - size, - NULL); - GNUNET_free (fn); - if (NULL == pb) - { - QRcode_free (qrc); - QRinput_free (qri); - return NULL; - } - pixels = gdk_pixbuf_get_pixels (pb); - n_channels = gdk_pixbuf_get_n_channels (pb); - for (unsigned int x = 4 * scale; x < size - 4 * scale; x++) - for (unsigned int y = 4 * scale; y < size - 4 * scale; y++) - { - unsigned int xx = x - 4 * scale; - unsigned int yy = y - 4 * scale; - unsigned int ss = size - 8 * scale; - unsigned int off = - (xx * qrc->width / ss) + (yy * qrc->width / ss) * qrc->width; - for (int c = 0; c < n_channels; c++) - pixels[(y * size + x) * n_channels + c] = - (0 == (qrc->data[off] & 1)) ? 0xFF : 0; - } - QRcode_free (qrc); - QRinput_free (qri); - return pb; -} - - -/** - * Create the QR code image for our zone. - * - * @param text text to encode - * @return NULL on error - */ -static GdkPixbuf * -setup_qrcode (const char *widget, - const char *text, - size_t text_size) -{ - GtkWidget *image; - GdkScreen *screen; - GtkSettings *settings; - gint dpi; - int scale; - - image = GTK_WIDGET (GCG_get_main_window_object (widget)); - if (NULL == image) - { - GNUNET_break (0); - return NULL; - } - /* adjust scale to screen resolution */ - screen = gtk_widget_get_screen (GTK_WIDGET (image)); - settings = gtk_settings_get_for_screen (screen); - g_object_get (G_OBJECT (settings), - "gtk-xft-dpi", - &dpi, - NULL); - if (-1 == dpi) - scale = 2; - else if (dpi >= 122800) - scale = 4; - else if (dpi >= 98304) - scale = 3; - else - scale = 2; - return create_qrcode (3 * scale, - text, - text_size); -} - - static void action_truths_paying (void) { @@ -1489,15 +1352,22 @@ action_truths_paying (void) { const char *payto = json_string_value (pt); GdkPixbuf *pb; + GtkWidget *w; + w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); + if (NULL == w) + { + GNUNET_break (0); + continue; + } if (NULL == payto) { GNUNET_break (0); continue; } - pb = setup_qrcode ("unpaid_qr_treeview", - payto, - strlen (payto)); + pb = AG_setup_qrcode (w, + payto, + strlen (payto)); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -1569,7 +1439,14 @@ action_policies_paying (void) GNUNET_JSON_spec_end () }; GdkPixbuf *pb; + GtkWidget *w; + w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); + if (NULL == w) + { + GNUNET_break (0); + continue; + } if (GNUNET_OK != GNUNET_JSON_parse (ppr, spec, @@ -1578,9 +1455,9 @@ action_policies_paying (void) GNUNET_break (0); continue; } - pb = setup_qrcode ("unpaid_qr_treeview", - payto, - strlen (payto)); + pb = AG_setup_qrcode (w, + payto, + strlen (payto)); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -1978,9 +1855,13 @@ show_challenge_feedback (void) } if (NULL != taler_pay_uri) { - qr = setup_qrcode ("anastasis_gtk_challenge_status_treeview", - taler_pay_uri, - strlen (taler_pay_uri)); + GtkWidget*w; + + w = GTK_WIDGET (GCG_get_main_window_object ( + "anastasis_gtk_challenge_status_treeview")); + qr = AG_setup_qrcode (w, + taler_pay_uri, + strlen (taler_pay_uri)); } if (TALER_EC_NONE != ec) emsg = TALER_ErrorCode_get_hint (ec); @@ -2575,6 +2456,7 @@ action_challenge_paying (void) GNUNET_JSON_spec_end () }; GdkPixbuf *pb; + GtkWidget *w; if (GNUNET_OK != GNUNET_JSON_parse (ppr, @@ -2593,9 +2475,10 @@ action_challenge_paying (void) "payment")) continue; found = true; - pb = setup_qrcode ("unpaid_qr_treeview", - payto, - strlen (payto)); + w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); + pb = AG_setup_qrcode (w, + payto, + strlen (payto)); if (NULL == pb) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Failed to initialize QR-code pixbuf for `%s'\n"), @@ -3179,22 +3062,22 @@ action_recovery_finished (void) AG_show ("anastasis_gtk_secret_copy_button"); } pb = NULL; + img = GTK_IMAGE (GCG_get_main_window_object ( + "anastasis_gtk_secret_qr_image")); if (NULL != text) { - pb = setup_qrcode ("anastasis_gtk_secret_qr_image", - text, - strlen (text)); + pb = AG_setup_qrcode (GTK_WIDGET (img), + text, + strlen (text)); } else { - pb = setup_qrcode ("anastasis_gtk_secret_qr_image", - data, - data_size); + pb = AG_setup_qrcode (GTK_WIDGET (img), + data, + data_size); } if (NULL != pb) { - img = GTK_IMAGE (GCG_get_main_window_object ( - "anastasis_gtk_secret_qr_image")); gtk_image_set_from_pixbuf (img, pb); g_object_unref (pb); diff --git a/src/anastasis/anastasis-gtk_handle-method-totp.c b/src/anastasis/anastasis-gtk_handle-method-totp.c new file mode 100644 index 0000000..c4faa5c --- /dev/null +++ b/src/anastasis/anastasis-gtk_handle-method-totp.c @@ -0,0 +1,235 @@ +/* + This file is part of anastasis-gtk. + Copyright (C) 2021 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + Anastasis is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Anastasis; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file src/anastasis/anastasis-gtk_handle-method-totp.c + * @brief Handle dialogs for TOTP (RFC 6238) + * @author Christian Grothoff + */ +#include <gnunet/platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "anastasis-gtk_action.h" +#include "anastasis-gtk_helper.h" +#include "anastasis-gtk_handle-identity-changed.h" +#include <jansson.h> + + +/** + * Random secret used in the current dialog. + */ +static char totp[32]; + + +/** + * Compute RFC 4648 base32 encoding of @a val and write + * result to @a enc. + * + * @param val value to encode + * @param val_size number of bytes in @a val + * @param[out] enc where to write the 0-terminated result + */ +static void +base32enc (const void *val, + size_t val_size, + char *enc) +{ + /** + * 32 characters for encoding, using RFC 4648. + */ + static char *encTable__ = "0123456789ABCDEFGHIJKLMNOPQRSTV"; + unsigned int wpos; + unsigned int rpos; + unsigned int bits; + unsigned int vbit; + const unsigned char *udata; + + udata = val; + vbit = 0; + wpos = 0; + rpos = 0; + bits = 0; + while ((rpos < val_size) || (vbit > 0)) + { + if ((rpos < val_size) && (vbit < 5)) + { + bits = (bits << 8) | udata[rpos++]; /* eat 8 more bits */ + vbit += 8; + } + if (vbit < 5) + { + bits <<= (5 - vbit); /* zero-padding */ + GNUNET_assert (vbit == ((val_size * 8) % 5)); + vbit = 5; + } + enc[wpos++] = encTable__[(bits >> (vbit - 5)) & 31]; + vbit -= 5; + } + GNUNET_assert (0 == vbit); + if (wpos < val_size) + enc[wpos] = '\0'; +} + + +/** + * Recompute the QR code shown in @a builder from + * totp and the user's name for the secret. + * + * @param builder the dialog builder + */ +static void +refresh_totp (GtkBuilder *builder) +{ + GtkEntry *q; + const char *name; + char *u_name; + char *uri; + char base_sec[sizeof (totp) * 2]; + GdkPixbuf *pb; + GtkImage *img; + + gtk_widget_set_sensitive ( + GTK_WIDGET (gtk_builder_get_object (builder, + "anastasis_gtk_b_totp_dialog_btn_ok")), + false); + q = GTK_ENTRY (gtk_builder_get_object (builder, + "anastasis_gtk_b_totp_dialog_name_entry")); + name = gtk_entry_get_text (q); + u_name = TALER_urlencode (name); + base32enc (totp, + sizeof (totp), + base_sec); + GNUNET_asprintf (&uri, + "otpauth://totp/%s?secret=%s", + u_name, + base_sec); + GNUNET_free (u_name); + img = GTK_IMAGE (gtk_builder_get_object (builder, + "qr_image")); + pb = AG_setup_qrcode (GTK_WIDGET (img), + uri, + strlen (uri)); + if (NULL != pb) + { + gtk_image_set_from_pixbuf (img, + pb); + g_object_unref (pb); + gtk_widget_set_sensitive ( + GTK_WIDGET (gtk_builder_get_object (builder, + "anastasis_gtk_b_totp_dialog_btn_ok")), + true); + } +} + + +/** + * Function called from the totp dialog upon completion. + * + * @param dialog the pseudonym selection dialog + * @param response_id response code from the dialog + * @param user_data the builder of the dialog + */ +void +anastasis_gtk_b_totp_dialog_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + GtkBuilder *builder = GTK_BUILDER (user_data); + GtkEntry *q; + const char *name; + json_t *args; + + if (GTK_RESPONSE_OK != response_id) + { + gtk_widget_destroy (GTK_WIDGET (dialog)); + g_object_unref (G_OBJECT (builder)); + return; + } + q = GTK_ENTRY (gtk_builder_get_object (builder, + "anastasis_gtk_b_totp_dialog_name_entry")); + name = gtk_entry_get_text (q); + args = json_pack ("{ s:{s:s, s:o, s:s}}", + "authentication_method", + "type", + "totp", + "challenge", + GNUNET_JSON_from_data (totp, + sizeof (totp)), + "instructions", + name); + gtk_widget_destroy (GTK_WIDGET (dialog)); + g_object_unref (G_OBJECT (builder)); + memset (totp, + 0, + sizeof (totp)); + AG_freeze (); + AG_ra = ANASTASIS_redux_action (AG_redux_state, + "add_authentication", + args, + &AG_action_cb, + NULL); + json_decref (args); +} + + +void +anastasis_gtk_b_totp_dialog_name_entry_changed_cb (GtkEntry *entry, + gpointer user_data) +{ + GtkBuilder *builder = GTK_BUILDER (user_data); + + refresh_totp (builder); +} + + +/** + * Callback invoked if the the "totp"-button is clicked. + * + * @param object + * @param user_data unused + */ +void +anastasis_gtk_btn_add_auth_totp_clicked_cb (GObject *object, + gpointer user_data) +{ + GtkWidget *ad; + GtkBuilder *builder; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + totp, + sizeof (totp)); + builder = GNUNET_GTK_get_new_builder ("anastasis_gtk_auth_add_totp.glade", + NULL); + if (NULL == builder) + { + GNUNET_break (0); + return; + } + ad = GTK_WIDGET (gtk_builder_get_object (builder, + "anastasis_gtk_b_totp_dialog")); + refresh_totp (builder); + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (object)); + gtk_window_set_transient_for (GTK_WINDOW (ad), + GTK_WINDOW (toplevel)); + gtk_window_present (GTK_WINDOW (ad)); + } +} diff --git a/src/anastasis/anastasis-gtk_helper.c b/src/anastasis/anastasis-gtk_helper.c index 84b369d..755561f 100644 --- a/src/anastasis/anastasis-gtk_helper.c +++ b/src/anastasis/anastasis-gtk_helper.c @@ -28,6 +28,8 @@ #include <gnunet/gnunet_util_lib.h> #include "anastasis-gtk_helper.h" #include <jansson.h> +#include <qrencode.h> +#include <gdk-pixbuf/gdk-pixbuf.h> /** @@ -263,3 +265,134 @@ AG_error (const char *format, AG_have_error = true; gtk_widget_show (GTK_WIDGET (l)); } + + +/** + * Create a the QR code image from a given @a text. + * + * @param scale factor for scaling up the size of the image to create + * @param text text to encode + * @return NULL on error + */ +static GdkPixbuf * +create_qrcode (unsigned int scale, + const char *text, + size_t text_size) +{ + QRinput *qri; + QRcode *qrc; + GdkPixbuf *pb; + guchar *pixels; + int n_channels; + const char *dir; + char *fn; + unsigned int size; + + qri = QRinput_new2 (0, QR_ECLEVEL_M); + if (NULL == qri) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2"); + return NULL; + } + /* first try encoding as uppercase-only alpha-numerical + QR code (much smaller encoding); if that fails, also + try using binary encoding (in case nick contains + special characters). */ + if ((0 != QRinput_append (qri, + QR_MODE_AN, + text_size, + (unsigned char *) text)) && + (0 != QRinput_append (qri, + QR_MODE_8, + text_size, + (unsigned char *) text))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "QRinput_append"); + return NULL; + } + qrc = QRcode_encodeInput (qri); + if (NULL == qrc) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "QRcode_encodeInput"); + QRinput_free (qri); + return NULL; + } + /* We use a trick to create a pixbuf in a way that works for both Gtk2 and + Gtk3 by loading a dummy file from disk; all other methods are not portable + to both Gtk2 and Gtk3. */ + dir = GNUNET_GTK_get_data_dir (); + GNUNET_asprintf (&fn, + "%s%s", + dir, + "qr_dummy.png"); + size = (qrc->width + 8) * scale; + size += 8 - (size % 8); + pb = gdk_pixbuf_new_from_file_at_size (fn, + size, + size, + NULL); + GNUNET_free (fn); + if (NULL == pb) + { + QRcode_free (qrc); + QRinput_free (qri); + return NULL; + } + pixels = gdk_pixbuf_get_pixels (pb); + n_channels = gdk_pixbuf_get_n_channels (pb); + for (unsigned int x = 4 * scale; x < size - 4 * scale; x++) + for (unsigned int y = 4 * scale; y < size - 4 * scale; y++) + { + unsigned int xx = x - 4 * scale; + unsigned int yy = y - 4 * scale; + unsigned int ss = size - 8 * scale; + unsigned int off = + (xx * qrc->width / ss) + (yy * qrc->width / ss) * qrc->width; + for (int c = 0; c < n_channels; c++) + pixels[(y * size + x) * n_channels + c] = + (0 == (qrc->data[off] & 1)) ? 0xFF : 0; + } + QRcode_free (qrc); + QRinput_free (qri); + return pb; +} + + +GdkPixbuf * +AG_setup_qrcode (GtkWidget *w, + const char *text, + size_t text_size) +{ + GtkWidget *image; + GdkScreen *screen; + GtkSettings *settings; + gint dpi; + int scale; + GdkPixbuf *pb; + + if (NULL == w) + { + GNUNET_break (0); + return NULL; + } + /* adjust scale to screen resolution */ + screen = gtk_widget_get_screen (w); + settings = gtk_settings_get_for_screen (screen); + g_object_get (G_OBJECT (settings), + "gtk-xft-dpi", + &dpi, + NULL); + if (-1 == dpi) + scale = 2; + else if (dpi >= 122800) + scale = 4; + else if (dpi >= 98304) + scale = 3; + else + scale = 2; + return create_qrcode (3 * scale, + text, + text_size); +} diff --git a/src/anastasis/anastasis-gtk_helper.h b/src/anastasis/anastasis-gtk_helper.h index 28ddc74..f76f71f 100644 --- a/src/anastasis/anastasis-gtk_helper.h +++ b/src/anastasis/anastasis-gtk_helper.h @@ -31,6 +31,7 @@ #include <anastasis/anastasis_service.h> #include <anastasis/anastasis_redux.h> #include "anastasis-gtk.h" +#include <gdk-pixbuf/gdk-pixbuf.h> /** @@ -476,4 +477,18 @@ void AG_error_clear (void); +/** + * Setup QR code image for a widget @a w. + * + * @param w widget to use to determine screen resolution + * @param text text to encode + * @param text_size number of bytes in @a text + * @return NULL on failure + */ +GdkPixbuf * +AG_setup_qrcode (GtkWidget *w, + const char *text, + size_t text_size); + + #endif |