mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 01:18:00 +00:00
98200d1bfe
DIT, for 'Data-Independent Timing', is a bit you can set in the processor state on sufficiently new Arm CPUs, which promises that a long list of instructions will deliberately avoid varying their timing based on the input register values. Just what you want for keeping your constant-time crypto primitives constant-time. As far as I'm aware, no CPU has _yet_ implemented any data-dependent optimisations, so DIT is a safety precaution against them doing so in future. It would be embarrassing to be caught without it if a future CPU does do that, so we now turn on DIT in the PuTTY process state. I've put a call to the new enable_dit() function at the start of every main() and WinMain() belonging to a program that might do cryptography (even testcrypt, in case someone uses it for something!), and in case I missed one there, also added a second call at the first moment that any cryptography-using part of the code looks as if it might become active: when an instance of the SSH protocol object is configured, when the system PRNG is initialised, and when selecting any cryptographic authentication protocol in an HTTP or SOCKS proxy connection. With any luck those precautions between them should ensure it's on whenever we need it. Arm's own recommendation is that you should carefully choose the granularity at which you enable and disable DIT: there's a potential time cost to turning it on and off (I'm not sure what, but plausibly something of the order of a pipeline flush), so it's a performance hit to do it _inside_ each individual crypto function, but if CPUs start supporting significant data-dependent optimisation in future, then it will also become a noticeable performance hit to just leave it on across the whole process. So you'd like to do it somewhere in the middle: for example, you might turn on DIT once around the whole process of verifying and decrypting an SSH packet, instead of once for decryption and once for MAC. With all respect to that recommendation as a strategy for maximum performance, I'm not following it here. I turn on DIT at the start of the PuTTY process, and then leave it on. Rationale: 1. PuTTY is not otherwise a performance-critical application: it's not likely to max out your CPU for any purpose _other_ than cryptography. The most CPU-intensive non-cryptographic thing I can imagine a PuTTY process doing is the complicated computation of font rendering in the terminal, and that will normally be cached (you don't recompute each glyph from its outline and hints for every time you display it). 2. I think a bigger risk lies in accidental side channels from having DIT turned off when it should have been on. I can imagine lots of causes for that. Missing a crypto operation in some unswept corner of the code; confusing control flow (like my coroutine macros) jumping with DIT clear into the middle of a region of code that expected DIT to have been set at the beginning; having a reference counter of DIT requests and getting it out of sync. In a more sophisticated programming language, it might be possible to avoid the risk in #2 by cleverness with the type system. For example, in Rust, you could have a zero-sized type that acts as a proof token for DIT being enabled (it would be constructed by a function that also sets DIT, have a Drop implementation that clears DIT, and be !Send so you couldn't use it in a thread other than the one where DIT was set), and then you could require all the actual crypto functions to take a DitToken as an extra parameter, at zero runtime cost. Then "oops I forgot to set DIT around this piece of crypto" would become a compile error. Even so, you'd have to take some care with coroutine-structured code (what happens if a Rust async function yields while holding a DIT token?) and with nesting (if you have two DIT tokens, you don't want dropping the inner one to clear DIT while the outer one is still there to wrongly convince callees that it's set). Maybe in Rust you could get this all to work reliably. But not in C! DIT is an optional feature of the Arm architecture, so we must first test to see if it's supported. This is done the same way as we already do for the various Arm crypto accelerators: on ELF-based systems, check the appropriate bit in the 'hwcap' words in the ELF aux vector; on Mac, look for an appropriate sysctl flag. On Windows I don't know of a way to query the DIT feature, _or_ of a way to write the necessary enabling instruction in an MSVC-compatible way. I've _heard_ that it might not be necessary, because Windows might just turn on DIT unconditionally and leave it on, in an even more extreme version of my own strategy. I don't have a source for that - I heard it by word of mouth - but I _hope_ it's true, because that would suit me very well! Certainly I can't write code to enable DIT without knowing (a) how to do it, (b) how to know if it's safe. Nonetheless, I've put the enable_dit() call in all the right places in the Windows main programs as well as the Unix and cross-platform code, so that if I later find out that I _can_ put in an explicit enable of DIT in some way, I'll only have to arrange to set HAVE_ARM_DIT and compile the enable_dit() function appropriately.
640 lines
19 KiB
C
640 lines
19 KiB
C
/*
|
|
* GTK implementation of a GUI password/passphrase prompt.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdk.h>
|
|
#if !GTK_CHECK_VERSION(3,0,0)
|
|
#include <gdk/gdkkeysyms.h>
|
|
#endif
|
|
|
|
#include "defs.h"
|
|
#include "unifont.h"
|
|
#include "gtkcompat.h"
|
|
#include "gtkmisc.h"
|
|
|
|
#include "putty.h"
|
|
#include "ssh.h"
|
|
#include "misc.h"
|
|
|
|
#define N_DRAWING_AREAS 3
|
|
|
|
struct drawing_area_ctx {
|
|
GtkWidget *area;
|
|
#ifndef DRAW_DEFAULT_CAIRO
|
|
GdkColor *cols;
|
|
#endif
|
|
int width, height;
|
|
enum { NOT_CURRENT, CURRENT, GREYED_OUT } state;
|
|
};
|
|
|
|
struct askpass_ctx {
|
|
GtkWidget *dialog, *promptlabel;
|
|
struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
|
|
int active_area;
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
GtkIMContext *imc;
|
|
#endif
|
|
#ifndef DRAW_DEFAULT_CAIRO
|
|
GdkColormap *colmap;
|
|
GdkColor cols[3];
|
|
#endif
|
|
char *error_message; /* if we finish without a passphrase */
|
|
strbuf *passphrase; /* if we finish with one */
|
|
#if GTK_CHECK_VERSION(3,20,0)
|
|
GdkSeat *seat; /* for gdk_seat_grab */
|
|
#elif GTK_CHECK_VERSION(3,0,0)
|
|
GdkDevice *keyboard; /* for gdk_device_grab */
|
|
#endif
|
|
|
|
int nattempts;
|
|
};
|
|
|
|
static prng *keypress_prng = NULL;
|
|
static void feed_keypress_prng(void *data, int size)
|
|
{
|
|
put_data(keypress_prng, data, size);
|
|
}
|
|
void random_add_noise(NoiseSourceId source, const void *noise, int length)
|
|
{
|
|
if (keypress_prng)
|
|
prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length));
|
|
}
|
|
static void setup_keypress_prng(void)
|
|
{
|
|
keypress_prng = prng_new(&ssh_sha256);
|
|
prng_seed_begin(keypress_prng);
|
|
noise_get_heavy(feed_keypress_prng);
|
|
prng_seed_finish(keypress_prng);
|
|
}
|
|
static void cleanup_keypress_prng(void)
|
|
{
|
|
prng_free(keypress_prng);
|
|
}
|
|
static uint64_t keypress_prng_value(void)
|
|
{
|
|
/*
|
|
* Don't actually put the passphrase keystrokes themselves into
|
|
* the PRNG; that doesn't seem like the course of wisdom when
|
|
* that's precisely what the information displayed on the screen
|
|
* is trying _not_ to be correlated to.
|
|
*/
|
|
noise_ultralight(NOISE_SOURCE_KEY, 0);
|
|
uint8_t data[8];
|
|
prng_read(keypress_prng, data, 8);
|
|
return GET_64BIT_MSB_FIRST(data);
|
|
}
|
|
static int choose_new_area(int prev_area)
|
|
{
|
|
int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1);
|
|
return (prev_area + 1 + reduced) % N_DRAWING_AREAS;
|
|
}
|
|
|
|
static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
|
|
{
|
|
int new_active = choose_new_area(ctx->active_area);
|
|
ctx->drawingareas[ctx->active_area].state = NOT_CURRENT;
|
|
gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
|
|
ctx->drawingareas[new_active].state = CURRENT;
|
|
gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
|
|
ctx->active_area = new_active;
|
|
}
|
|
|
|
static size_t last_char_start(struct askpass_ctx *ctx)
|
|
{
|
|
/*
|
|
* GTK always encodes in UTF-8, so we can do this in a fixed way.
|
|
*/
|
|
assert(ctx->passphrase->len > 0);
|
|
size_t i = ctx->passphrase->len - 1;
|
|
while ((unsigned)(ctx->passphrase->u[i] - 0x80) < 0x40) {
|
|
if (i == 0)
|
|
break;
|
|
i--;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
|
|
{
|
|
put_datapl(ctx->passphrase, ptrlen_from_asciz(str));
|
|
visually_acknowledge_keypress(ctx);
|
|
}
|
|
|
|
static void cancel_askpass(struct askpass_ctx *ctx, const char *msg)
|
|
{
|
|
strbuf_free(ctx->passphrase);
|
|
ctx->passphrase = NULL;
|
|
ctx->error_message = dupstr(msg);
|
|
gtk_main_quit();
|
|
}
|
|
|
|
static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event,
|
|
gpointer data)
|
|
{
|
|
struct askpass_ctx *ctx = (struct askpass_ctx *)data;
|
|
cancel_askpass(ctx, "passphrase input cancelled");
|
|
/* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */
|
|
return true;
|
|
}
|
|
|
|
static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
|
|
{
|
|
struct askpass_ctx *ctx = (struct askpass_ctx *)data;
|
|
|
|
if (event->keyval == GDK_KEY_Return &&
|
|
event->type == GDK_KEY_PRESS) {
|
|
gtk_main_quit();
|
|
} else if (event->keyval == GDK_KEY_Escape &&
|
|
event->type == GDK_KEY_PRESS) {
|
|
cancel_askpass(ctx, "passphrase input cancelled");
|
|
} else {
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
if (gtk_im_context_filter_keypress(ctx->imc, event))
|
|
return true;
|
|
#endif
|
|
|
|
if (event->type == GDK_KEY_PRESS) {
|
|
if (!strcmp(event->string, "\x15")) {
|
|
/* Ctrl-U. Wipe out the whole line */
|
|
strbuf_clear(ctx->passphrase);
|
|
visually_acknowledge_keypress(ctx);
|
|
} else if (!strcmp(event->string, "\x17")) {
|
|
/* Ctrl-W. Delete back to the last space->nonspace
|
|
* boundary. We interpret 'space' in a really simple
|
|
* way (mimicking terminal drivers), and don't attempt
|
|
* to second-guess exciting Unicode space
|
|
* characters. */
|
|
while (ctx->passphrase->len > 0) {
|
|
char deleted, prior;
|
|
size_t newlen = last_char_start(ctx);
|
|
deleted = ctx->passphrase->s[newlen];
|
|
strbuf_shrink_to(ctx->passphrase, newlen);
|
|
prior = (ctx->passphrase->len == 0 ? ' ' :
|
|
ctx->passphrase->s[ctx->passphrase->len-1]);
|
|
if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
|
|
break;
|
|
}
|
|
visually_acknowledge_keypress(ctx);
|
|
} else if (event->keyval == GDK_KEY_BackSpace) {
|
|
/* Backspace. Delete one character. */
|
|
if (ctx->passphrase->len > 0)
|
|
strbuf_shrink_to(ctx->passphrase, last_char_start(ctx));
|
|
visually_acknowledge_keypress(ctx);
|
|
#if !GTK_CHECK_VERSION(2,0,0)
|
|
} else if (event->string[0]) {
|
|
add_text_to_passphrase(ctx, event->string);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
static void input_method_commit_event(GtkIMContext *imc, gchar *str,
|
|
gpointer data)
|
|
{
|
|
struct askpass_ctx *ctx = (struct askpass_ctx *)data;
|
|
add_text_to_passphrase(ctx, str);
|
|
}
|
|
#endif
|
|
|
|
static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
|
|
gpointer data)
|
|
{
|
|
struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
|
|
ctx->width = event->width;
|
|
ctx->height = event->height;
|
|
gtk_widget_queue_draw(widget);
|
|
return true;
|
|
}
|
|
|
|
#ifdef DRAW_DEFAULT_CAIRO
|
|
static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
|
|
{
|
|
double rgbval = (ctx->state == CURRENT ? 0 :
|
|
ctx->state == NOT_CURRENT ? 1 : 0.5);
|
|
cairo_set_source_rgb(cr, rgbval, rgbval, rgbval);
|
|
cairo_paint(cr);
|
|
}
|
|
#else
|
|
static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
|
|
{
|
|
GdkGC *gc = gdk_gc_new(win);
|
|
gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]);
|
|
gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height);
|
|
gdk_gc_unref(gc);
|
|
}
|
|
#endif
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
|
|
{
|
|
struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
|
|
askpass_redraw_cairo(cr, ctx);
|
|
return true;
|
|
}
|
|
#else
|
|
static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
|
|
gpointer data)
|
|
{
|
|
struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
|
|
|
|
#ifdef DRAW_DEFAULT_CAIRO
|
|
cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
|
|
askpass_redraw_cairo(cr, ctx);
|
|
cairo_destroy(cr);
|
|
#else
|
|
askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static gboolean try_grab_keyboard(gpointer vctx)
|
|
{
|
|
struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
|
|
int i, ret;
|
|
|
|
#if GTK_CHECK_VERSION(3,20,0)
|
|
/*
|
|
* Grabbing the keyboard in GTK 3.20 requires the new notion of
|
|
* GdkSeat.
|
|
*/
|
|
GdkSeat *seat;
|
|
GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog);
|
|
if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw))
|
|
goto fail;
|
|
|
|
seat = gdk_display_get_default_seat(
|
|
gtk_widget_get_display(ctx->dialog));
|
|
if (!seat)
|
|
goto fail;
|
|
|
|
ctx->seat = seat;
|
|
ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD,
|
|
true, NULL, NULL, NULL, NULL);
|
|
|
|
/*
|
|
* For some reason GDK 3.22 hides the GDK window as a side effect
|
|
* of a failed grab. I've no idea why. But if we're going to retry
|
|
* the grab, then we need to unhide it again or else we'll just
|
|
* get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt.
|
|
*/
|
|
if (ret != GDK_GRAB_SUCCESS)
|
|
gdk_window_show(gdkw);
|
|
|
|
#elif GTK_CHECK_VERSION(3,0,0)
|
|
/*
|
|
* And it has to be done differently again prior to GTK 3.20.
|
|
*/
|
|
GdkDeviceManager *dm;
|
|
GdkDevice *pointer, *keyboard;
|
|
|
|
dm = gdk_display_get_device_manager(
|
|
gtk_widget_get_display(ctx->dialog));
|
|
if (!dm)
|
|
goto fail;
|
|
|
|
pointer = gdk_device_manager_get_client_pointer(dm);
|
|
if (!pointer)
|
|
goto fail;
|
|
keyboard = gdk_device_get_associated_device(pointer);
|
|
if (!keyboard)
|
|
goto fail;
|
|
if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
|
|
goto fail;
|
|
|
|
ctx->keyboard = keyboard;
|
|
ret = gdk_device_grab(ctx->keyboard,
|
|
gtk_widget_get_window(ctx->dialog),
|
|
GDK_OWNERSHIP_NONE,
|
|
true,
|
|
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
|
|
NULL,
|
|
GDK_CURRENT_TIME);
|
|
#else
|
|
/*
|
|
* It's much simpler in GTK 1 and 2!
|
|
*/
|
|
ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
|
|
false, GDK_CURRENT_TIME);
|
|
#endif
|
|
if (ret != GDK_GRAB_SUCCESS)
|
|
goto fail;
|
|
|
|
/*
|
|
* Now that we've got the keyboard grab, connect up our keyboard
|
|
* handlers.
|
|
*/
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
g_signal_connect(G_OBJECT(ctx->imc), "commit",
|
|
G_CALLBACK(input_method_commit_event), ctx);
|
|
#endif
|
|
g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event",
|
|
G_CALLBACK(key_event), ctx);
|
|
g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event",
|
|
G_CALLBACK(key_event), ctx);
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
gtk_im_context_set_client_window(ctx->imc,
|
|
gtk_widget_get_window(ctx->dialog));
|
|
#endif
|
|
|
|
/*
|
|
* And repaint the key-acknowledgment drawing areas as not greyed
|
|
* out.
|
|
*/
|
|
ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS;
|
|
for (i = 0; i < N_DRAWING_AREAS; i++) {
|
|
ctx->drawingareas[i].state =
|
|
(i == ctx->active_area ? CURRENT : NOT_CURRENT);
|
|
gtk_widget_queue_draw(ctx->drawingareas[i].area);
|
|
}
|
|
|
|
return false;
|
|
|
|
fail:
|
|
/*
|
|
* If we didn't get the grab, reschedule ourself on a timer to try
|
|
* again later.
|
|
*
|
|
* We have to do this rather than just trying once, because there
|
|
* is at least one important situation in which the grab may fail
|
|
* the first time: any user who is launching an add-key operation
|
|
* off some kind of window manager hotkey will almost by
|
|
* definition be running this script with a keyboard grab already
|
|
* active, namely the one-key grab that the WM (or whatever) uses
|
|
* to detect presses of the hotkey. So at the very least we have
|
|
* to give the user time to release that key.
|
|
*/
|
|
if (++ctx->nattempts >= 4) {
|
|
cancel_askpass(ctx, "unable to grab keyboard after 5 seconds");
|
|
} else {
|
|
g_timeout_add(1000/8, try_grab_keyboard, ctx);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void realize(GtkWidget *widget, gpointer vctx)
|
|
{
|
|
struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
|
|
|
|
gtk_grab_add(ctx->dialog);
|
|
|
|
/*
|
|
* Schedule the first attempt at the keyboard grab.
|
|
*/
|
|
ctx->nattempts = 0;
|
|
#if GTK_CHECK_VERSION(3,20,0)
|
|
ctx->seat = NULL;
|
|
#elif GTK_CHECK_VERSION(3,0,0)
|
|
ctx->keyboard = NULL;
|
|
#endif
|
|
|
|
g_idle_add(try_grab_keyboard, ctx);
|
|
}
|
|
|
|
static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
|
|
const char *window_title,
|
|
const char *prompt_text)
|
|
{
|
|
int i;
|
|
GtkBox *action_area;
|
|
|
|
ctx->passphrase = strbuf_new_nm();
|
|
|
|
/*
|
|
* Create widgets.
|
|
*/
|
|
ctx->dialog = our_dialog_new();
|
|
gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
|
|
gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
|
|
g_signal_connect(G_OBJECT(ctx->dialog), "delete-event",
|
|
G_CALLBACK(askpass_dialog_closed), ctx);
|
|
ctx->promptlabel = gtk_label_new(prompt_text);
|
|
align_label_left(GTK_LABEL(ctx->promptlabel));
|
|
gtk_widget_show(ctx->promptlabel);
|
|
gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true);
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
|
|
#endif
|
|
int margin = string_width("MM");
|
|
#if GTK_CHECK_VERSION(3,12,0)
|
|
gtk_widget_set_margin_start(ctx->promptlabel, margin);
|
|
gtk_widget_set_margin_end(ctx->promptlabel, margin);
|
|
#else
|
|
gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0);
|
|
#endif
|
|
our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
|
|
ctx->promptlabel, true, true, 0);
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
ctx->imc = gtk_im_multicontext_new();
|
|
#endif
|
|
#ifndef DRAW_DEFAULT_CAIRO
|
|
{
|
|
gboolean success[2];
|
|
ctx->colmap = gdk_colormap_get_system();
|
|
ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
|
|
ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
|
|
ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000;
|
|
gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
|
|
false, true, success);
|
|
if (!success[0] || !success[1])
|
|
return "unable to allocate colours";
|
|
}
|
|
#endif
|
|
|
|
action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
|
|
|
|
for (i = 0; i < N_DRAWING_AREAS; i++) {
|
|
ctx->drawingareas[i].area = gtk_drawing_area_new();
|
|
#ifndef DRAW_DEFAULT_CAIRO
|
|
ctx->drawingareas[i].cols = ctx->cols;
|
|
#endif
|
|
ctx->drawingareas[i].state = GREYED_OUT;
|
|
ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
|
|
/* It would be nice to choose this size in some more
|
|
* context-sensitive way, like measuring the size of some
|
|
* piece of template text. */
|
|
gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
|
|
gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
|
|
true, true, 5);
|
|
g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
|
|
"configure_event",
|
|
G_CALLBACK(configure_area),
|
|
&ctx->drawingareas[i]);
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
|
|
"draw",
|
|
G_CALLBACK(draw_area),
|
|
&ctx->drawingareas[i]);
|
|
#else
|
|
g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
|
|
"expose_event",
|
|
G_CALLBACK(expose_area),
|
|
&ctx->drawingareas[i]);
|
|
#endif
|
|
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
g_object_set(G_OBJECT(ctx->drawingareas[i].area),
|
|
"margin-bottom", 8, (const char *)NULL);
|
|
#endif
|
|
|
|
gtk_widget_show(ctx->drawingareas[i].area);
|
|
}
|
|
ctx->active_area = -1;
|
|
|
|
/*
|
|
* Arrange to receive key events. We don't really need to worry
|
|
* from a UI perspective about which widget gets the events, as
|
|
* long as we know which it is so we can catch them. So we'll pick
|
|
* the prompt label at random, and we'll use gtk_grab_add to
|
|
* ensure key events go to it.
|
|
*/
|
|
gtk_widget_set_sensitive(ctx->dialog, true);
|
|
|
|
#if GTK_CHECK_VERSION(2,0,0)
|
|
gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true);
|
|
#endif
|
|
|
|
/*
|
|
* Wait for the key-receiving widget to actually be created, in
|
|
* order to call gtk_grab_add on it.
|
|
*/
|
|
g_signal_connect(G_OBJECT(ctx->dialog), "realize",
|
|
G_CALLBACK(realize), ctx);
|
|
|
|
/*
|
|
* Show the window.
|
|
*/
|
|
gtk_widget_show(ctx->dialog);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
|
|
{
|
|
#if GTK_CHECK_VERSION(3,20,0)
|
|
if (ctx->seat)
|
|
gdk_seat_ungrab(ctx->seat);
|
|
#elif GTK_CHECK_VERSION(3,0,0)
|
|
if (ctx->keyboard)
|
|
gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
|
|
#else
|
|
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
|
|
#endif
|
|
gtk_grab_remove(ctx->promptlabel);
|
|
|
|
gtk_widget_destroy(ctx->dialog);
|
|
}
|
|
|
|
static bool setup_gtk(const char *display)
|
|
{
|
|
static bool gtk_initialised = false;
|
|
int argc;
|
|
char *real_argv[3];
|
|
char **argv = real_argv;
|
|
bool ret;
|
|
|
|
if (gtk_initialised)
|
|
return true;
|
|
|
|
argc = 0;
|
|
argv[argc++] = dupstr("dummy");
|
|
argv[argc++] = dupprintf("--display=%s", display);
|
|
argv[argc] = NULL;
|
|
ret = gtk_init_check(&argc, &argv);
|
|
while (argc > 0)
|
|
sfree(argv[--argc]);
|
|
|
|
gtk_initialised = ret;
|
|
return ret;
|
|
}
|
|
|
|
const bool buildinfo_gtk_relevant = true;
|
|
|
|
char *gtk_askpass_main(const char *display, const char *wintitle,
|
|
const char *prompt, bool *success)
|
|
{
|
|
struct askpass_ctx ctx[1];
|
|
const char *err;
|
|
|
|
ctx->passphrase = NULL;
|
|
ctx->error_message = NULL;
|
|
|
|
/* In case gtk_init hasn't been called yet by the program */
|
|
if (!setup_gtk(display)) {
|
|
*success = false;
|
|
return dupstr("unable to initialise GTK");
|
|
}
|
|
|
|
if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
|
|
*success = false;
|
|
return dupprintf("%s", err);
|
|
}
|
|
setup_keypress_prng();
|
|
gtk_main();
|
|
cleanup_keypress_prng();
|
|
gtk_askpass_cleanup(ctx);
|
|
|
|
if (ctx->passphrase) {
|
|
*success = true;
|
|
return strbuf_to_str(ctx->passphrase);
|
|
} else {
|
|
*success = false;
|
|
return ctx->error_message;
|
|
}
|
|
}
|
|
|
|
#ifdef TEST_ASKPASS
|
|
void modalfatalbox(const char *p, ...)
|
|
{
|
|
va_list ap;
|
|
fprintf(stderr, "FATAL ERROR: ");
|
|
va_start(ap, p);
|
|
vfprintf(stderr, p, ap);
|
|
va_end(ap);
|
|
fputc('\n', stderr);
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
bool success;
|
|
int exitcode;
|
|
char *ret;
|
|
|
|
enable_dit(); /* maybe overkill, but we _are_ handling a secret */
|
|
|
|
gtk_init(&argc, &argv);
|
|
|
|
if (argc != 2) {
|
|
success = false;
|
|
ret = dupprintf("usage: %s <prompt text>", argv[0]);
|
|
} else {
|
|
ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
|
|
}
|
|
|
|
if (!success) {
|
|
fputs(ret, stderr);
|
|
fputc('\n', stderr);
|
|
exitcode = 1;
|
|
} else {
|
|
fputs(ret, stdout);
|
|
fputc('\n', stdout);
|
|
exitcode = 0;
|
|
}
|
|
|
|
smemclr(ret, strlen(ret));
|
|
return exitcode;
|
|
}
|
|
#endif
|