aboutsummaryrefslogtreecommitdiffstats
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-rw-r--r--misc/gtk/GNUmakefile24
-rw-r--r--misc/gtk/dsa.cpp552
-rw-r--r--misc/gtk/gtk_ui.cpp78
-rw-r--r--misc/gtk/gtk_ui.h25
-rw-r--r--misc/gtk/readme.txt22
5 files changed, 701 insertions, 0 deletions
diff --git a/misc/gtk/GNUmakefile b/misc/gtk/GNUmakefile
new file mode 100644
index 000000000..d0e81505b
--- /dev/null
+++ b/misc/gtk/GNUmakefile
@@ -0,0 +1,24 @@
+
+CC = g++
+
+LIBS=$(shell botan-config --libs) $(shell pkg-config --libs gtk+-2.0)
+IFLAGS=$(shell botan-config --cflags) $(shell pkg-config --cflags gtk+-2.0)
+FLAGS=-Wall -W $(IFLAGS)
+
+SOURCE=$(wildcard *.cpp)
+HEADERS=$(wildcard *.h)
+
+OBJS=$(patsubst %.cpp,%.o,$(SOURCE))
+
+APP=dsa
+
+all: $(APP)
+
+$(APP): $(OBJS)
+ $(CC) $(OBJS) $(LIBS) -o $(APP)
+
+%.o: %.cpp $(HEADERS)
+ $(CC) $(FLAGS) -c $<
+
+clean:
+ rm -f $(APP) $(OBJS)
diff --git a/misc/gtk/dsa.cpp b/misc/gtk/dsa.cpp
new file mode 100644
index 000000000..a5471e9c0
--- /dev/null
+++ b/misc/gtk/dsa.cpp
@@ -0,0 +1,552 @@
+/*
+ This shows some of the details involved in a GUI application that uses
+ Botan. Actually most of the code is just dealing with GTK+, but it shows how
+ the password callback and pulse function stuff works. (See gtk_ui.cpp for the
+ acutal password callback code.)
+
+ The major points of interest (assuming what you care about is how to use
+ Botan from a GUI, and not looking at my terrible GTK code) are gtk_ui.cpp
+ and, in this file, gtk_pulse(), gen_key(), and get_key():
+
+ gtk_ui.cpp and get_key() show how to get a passphrase from a user for
+ decrypting (well, in theory, anything), but in this case, PKCS #8 private
+ keys. Which is coincidentally the only thing Botan currently uses UI
+ objects for, though that will probably change eventually. GTK_UI does
+ double duty, for getting passphrases for encryption as well (in
+ do_save_key).
+
+ gen_key() and gtk_pulse() show how to do an activity meter while doing a
+ long-term operation inside Botan. Since, typically, the only operations
+ which take a long time and can't be broken up into smaller parts are prime
+ generation/testing, that is currently where the pulse hooks are
+ called. It's certainly not the most general callback method in the world,
+ but it's general enough that it's usable without getting in the way too
+ much. The callbacks will eventually be extended to other parts of the
+ library (Pipe, maybe) where it's useful.
+
+ This program is in the public domain.
+*/
+#include <fstream>
+#include <iostream>
+#include <memory>
+
+#include <botan/botan.h>
+#include <botan/look_pk.h>
+#include <botan/filters.h>
+#include <botan/dsa.h>
+// we don't have a 'using namespace' here, so it's easy to grep for code that
+// is actually dealing with the library (rather than messing around with GTK).
+
+#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,4,4)
+ #error "You need a more recent version of Botan (at least 1.4.4)"
+#endif
+
+#include <gtk/gtk.h>
+#include "gtk_ui.h"
+
+/*************************************************
+* Pop up an message box *
+*************************************************/
+static void show_dialog(const std::string& about_message,
+ const std::string& dialog_name)
+ {
+ GtkDialogFlags flags =
+ (GtkDialogFlags)(GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL);
+
+ GtkWidget* dialog = gtk_dialog_new_with_buttons(dialog_name.c_str(),
+ NULL, flags,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_NONE,
+ NULL);
+ GtkWidget* label = gtk_label_new(NULL);
+ gtk_label_set_markup(GTK_LABEL(label), about_message.c_str());
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
+
+ g_signal_connect_swapped(GTK_OBJECT(dialog), "response",
+ G_CALLBACK(gtk_widget_destroy),
+ GTK_OBJECT(dialog));
+
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
+ gtk_widget_show_all(dialog);
+ }
+
+/*************************************************
+* Pop up an About box *
+*************************************************/
+static void show_about()
+ {
+ const std::string about_message =
+ "<big>DSA Utility</big>\n"
+ "\n"
+ "A simple application showing how to use Botan within a GUI.\n"
+ "It lets you generate or load keys, and sign text files.\n"
+ "\n"
+ "Send comments/questions to <tt>[email protected]</tt>";
+
+ show_dialog(about_message, "About");
+ }
+
+/*************************************************
+* Pop up a help box *
+*************************************************/
+static void show_help()
+ {
+ const std::string help_message =
+ "<big>DSA Utility Help</big>\n"
+ "\n"
+ "Simply, this is a (very) simple text editor, with the added ability\n"
+ "of being able to generate or read a DSA private key, and sign the\n"
+ "text buffer using that key.\n"
+ "\n"
+ "You can load, edit, and save text files as you would normally. If a\n"
+ "key is loaded (done using the commands in the Keys menu), you can\n"
+ "also use the Sign command (in the Signing menu) to generate a\n"
+ "signature for the current file. It will be displayed at the bottom\n"
+ "of the screen (if it has been calculated for the current buffer\n"
+ "contents), and can be saved using the \"Save Sig\" command.\n"
+ "\n"
+ "Signatures generated by this program can be verified using a the\n"
+ "<tt>dsa_ver</tt> example included in the Botan distribution.\n";
+
+ show_dialog(help_message, "Help");
+ }
+
+/*************************************************
+* Get and return a filename from the user *
+*************************************************/
+static std::string get_filename(const std::string& title)
+ {
+ GtkWidget* dialog = gtk_file_selection_new(title.c_str());
+
+ /* Some window managers don't display the titles of transient windows,
+ put a message elsewhere for those people.
+ */
+ GtkWidget* label = gtk_label_new(title.c_str());
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
+
+ std::string fsname;
+
+ gtk_widget_show(label); /* dialog_run won't show sub-widgets */
+ if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
+ fsname = gtk_file_selection_get_filename(GTK_FILE_SELECTION(dialog));
+ gtk_widget_destroy(dialog);
+
+ /* If it's a directory, that's no good */
+ if(fsname.size() && fsname[fsname.size()-1] == '/')
+ return "";
+
+ return fsname;
+ }
+
+/*************************************************
+* Global state *
+*************************************************/
+static Botan::DSA_PrivateKey* key = 0; // our key
+static GtkTextBuffer* buffer = 0; // the text buffer
+static std::string buffer_source;
+ // what file (if any) the buffer's data came from
+static GtkWidget* sig_view = 0; // the signature
+
+/*************************************************
+* Zap the currently set signature (if any) *
+*************************************************/
+static void zap_sig()
+ {
+ gtk_editable_delete_text(GTK_EDITABLE(sig_view), 0, -1);
+ }
+
+/*************************************************
+* Save the current key *
+*************************************************/
+static void do_save_key(const std::string& title)
+ {
+ if(key == 0)
+ return;
+
+ std::string filename = get_filename(title.c_str());
+
+ if(filename != "")
+ {
+ const std::string msg = "Select a passphrase to encrypt the key:";
+
+ std::ofstream out_priv(filename.c_str());
+
+ GTK_UI ui;
+ Botan::User_Interface::UI_Result result;
+ std::string passphrase = ui.get_passphrase(msg, result);
+
+ if(result == Botan::User_Interface::OK)
+ out_priv << Botan::PKCS8::PEM_encode(*key, passphrase);
+ else
+ out_priv << Botan::PKCS8::PEM_encode(*key);
+
+ // for testing
+ //std::cout << X509::PEM_encode(*key);
+ }
+ }
+
+/*************************************************
+* Generate a signature for the text buffer *
+*************************************************/
+static void sign_buffer()
+ {
+ /* No key? Ignore request. */
+ if(key == 0)
+ return;
+
+ /* same format as the text-mode dsa_sign example */
+ Botan::Pipe pipe(new Botan::PK_Signer_Filter(
+ Botan::get_pk_signer(*key, "EMSA1(SHA-1)")
+ ),
+ new Botan::Base64_Encoder
+ );
+
+ /* It would probably be smart to do this a little bit at a time */
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(buffer, &start, &end);
+ gchar* bits = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+ size_t length = strlen(bits);
+
+ pipe.start_msg();
+ pipe.write((unsigned char*)bits, length);
+ pipe.end_msg();
+
+ std::string sig = pipe.read_all_as_string();
+
+ zap_sig();
+
+ gint position = 0;
+ gtk_editable_insert_text(GTK_EDITABLE(sig_view), sig.c_str(), sig.length(),
+ &position);
+
+ g_free(bits);
+ }
+
+/*************************************************
+* GTK+ pulse callback *
+*************************************************/
+static void gtk_pulse(Botan::UI::Pulse_Type, void*)
+ {
+ /* We need this to flush the udpates, otherwise GTK+ will wait until we're
+ done with the computation before doing any updates (generally the right
+ thing, but not with a progress bar).
+ */
+ while(gtk_events_pending())
+ gtk_main_iteration();
+ }
+
+/*************************************************
+* Actual do the pulse (as a GTK+ timeout func) *
+*************************************************/
+static gboolean gtk_pulse_timeout(void* pbar)
+ {
+ GtkWidget* progress_bar = (GtkWidget*)pbar;
+ gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progress_bar));
+ return TRUE; /* keep calling us */
+ }
+
+/*************************************************
+* Generate a new key *
+*************************************************/
+static void gen_key()
+ {
+ /* This gives a nice smooth progress bar, though we do use up quite a bit of
+ CPU for it. Keep in mind that if PULSE_INTERVAL is significantly less
+ than the average time between pulses from the library, the progress bar
+ will jerk around going slower or faster. Keep it at at least 50ms.
+ */
+ const double PROGRESS_PER_PULSE = .01; /* % of bar */
+ const guint32 PULSE_INTERVAL = 30; /* ms */
+
+ delete key;
+
+ GtkDialogFlags flags =
+ (GtkDialogFlags)(GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL);
+
+ GtkWidget* dialog =
+ gtk_dialog_new_with_buttons("Generating Key", NULL, flags, NULL);
+
+ GtkWidget* label = gtk_label_new(" Generating new key, please wait... \n");
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
+
+ GtkWidget* progress_bar = gtk_progress_bar_new();
+ gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(progress_bar),
+ PROGRESS_PER_PULSE);
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), progress_bar);
+
+ guint timer_id = gtk_timeout_add(PULSE_INTERVAL, gtk_pulse_timeout,
+ progress_bar);
+
+ gtk_widget_show_all(dialog);
+
+ while(gtk_events_pending())
+ gtk_main_iteration();
+
+ /* Register gtk_pulse so it will be called every so often when we embark
+ on our prime generation quest...
+ */
+ /* this just updates the GUI; the GTK+ timeout function actually updates
+ the progress bar. That's because the amount of time between pulses
+ from the library is rather irregular, so the progress bar looks jerky.
+ */
+ Botan::UI::set_pulse(gtk_pulse);
+
+ /* Not generally recommended, since it's slow and there's not much point.
+ However, *because* it's slow, we'll want to put up a progress bar or
+ something, and part of this whole thing is to show how to do that and get
+ the pulse functions to do the right thing.
+ */
+ Botan::DL_Group group(1024, Botan::DL_Group::DSA_Kosherizer);
+ key = new Botan::DSA_PrivateKey(group);
+
+ gtk_timeout_remove(timer_id);
+ Botan::UI::set_pulse(0); // unset the pulse function
+
+ gtk_widget_destroy(dialog);
+
+ do_save_key("Save New Key");
+
+ /* new key, any old sigs are no longer useful */
+ zap_sig();
+ }
+
+/*************************************************
+* Load up a key *
+*************************************************/
+static void get_key()
+ {
+ std::string fsname = get_filename("Select a DSA Key");
+
+ if(fsname != "")
+ {
+ try {
+ delete key;
+ key = 0;
+ zap_sig();
+
+ /*
+ A GTK_UI is a subclass of User_Interface that pops up a dialog that
+ asks the user for a passphrase. It actually works quite well,
+ though the fixed upper limit on the passphrase size is not
+ ideal. Feel free to use it as-is or modify it however you like
+ (gtk_ui.* is public domain).
+ */
+ GTK_UI ui;
+ Botan::PKCS8_PrivateKey* p8_key = Botan::PKCS8::load_key(fsname, ui);
+ key = dynamic_cast<Botan::DSA_PrivateKey*>(p8_key);
+ if(!key)
+ show_dialog("The key in " + fsname + " is not a DSA key",
+ "Failure");
+ }
+ catch(std::exception)
+ {
+ key = 0; // make sure it's not something random
+ show_dialog("Loading the key from " + fsname + " failed.", "Failure");
+ }
+ }
+ }
+
+static void really_sign_buffer()
+ {
+ /* No key? Ask the user for one. */
+ if(key == 0)
+ get_key();
+ sign_buffer();
+ }
+
+/*************************************************
+* Clear the text buffer *
+*************************************************/
+static void new_buffer()
+ {
+ /*
+ In theory, it would be nice to check if this was unsaved text and prompt
+ to save it. However, this really isn't supposed to be a GTK+ example, so
+ we won't.
+ */
+ gtk_text_buffer_set_text(buffer, "", -1);
+ buffer_source = "";
+ }
+
+/*************************************************
+* Put the contents of a file into the buffer *
+*************************************************/
+static void open_buffer()
+ {
+ std::string filename = get_filename("Select File");
+
+ if(filename == "")
+ return;
+
+ std::ifstream in(filename.c_str());
+
+ new_buffer();
+ buffer_source = filename;
+
+ while(in.good())
+ {
+ char buf[1024] = { 0 };
+
+ in.read(buf, 1024);
+ size_t got = in.gcount();
+
+ GtkTextIter iter;
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_buffer_insert(buffer, &iter, buf, got);
+ }
+ }
+
+/*************************************************
+* Save the signature to a file *
+*************************************************/
+static void save_sig()
+ {
+ std::string sig_file = buffer_source;
+
+ /* No sig, nothing to save */
+ const gchar* sig = gtk_entry_get_text(GTK_ENTRY(sig_view));
+ if(strlen(sig) == 0)
+ return;
+
+ if(sig_file == "")
+ sig_file = get_filename("Select Signature Output File");
+ else
+ sig_file += ".sig";
+
+ std::ofstream out(sig_file.c_str());
+ out << sig << std::endl;
+ }
+
+/*************************************************
+* Save the current key *
+*************************************************/
+static void save_key()
+ {
+ do_save_key("Save Current Key");
+ }
+
+/*************************************************
+* Common case of Save/Save As *
+*************************************************/
+static void do_save(const std::string& filename)
+ {
+ std::ofstream out(filename.c_str());
+
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(buffer, &start, &end);
+ gchar* bits = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+ out.write(bits, strlen(bits));
+ g_free(bits);
+ buffer_source = filename;
+ }
+
+/*************************************************
+* Save the buffer *
+*************************************************/
+static void save_buffer_as()
+ {
+ std::string filename = get_filename("Select Output File");
+ if(filename != "")
+ do_save(filename);
+ }
+
+/*************************************************
+* Save the buffer *
+*************************************************/
+static void save_buffer()
+ {
+ if(buffer_source != "")
+ do_save(buffer_source);
+ else
+ save_buffer_as();
+ }
+
+/*************************************************
+* Make a menubar for the app *
+*************************************************/
+static GtkWidget* make_menubar(GtkWidget *window)
+ {
+ static GtkItemFactoryEntry menu_items[] = {
+ { "/_File", NULL, NULL, 0, "<Branch>", NULL },
+ { "/File/_New", "<control>N", new_buffer, 0, NULL, NULL },
+ { "/File/_Open", "<control>O", open_buffer, 0, NULL, NULL },
+ { "/File/_Save", "<control>S", save_buffer, 0, NULL, NULL },
+ { "/File/Save _As", NULL, save_buffer_as, 0, NULL, NULL },
+ { "/File/sep1", NULL, NULL, 0, "<Separator>", NULL },
+ { "/File/Save Sig", NULL, save_sig, 0, NULL, NULL },
+ { "/File/sep2", NULL, NULL, 0, "<Separator>", NULL },
+ { "/File/_Quit", "<control>Q", gtk_main_quit, 0, NULL, NULL },
+
+ { "/_Keys", NULL, NULL, 0, "<Branch>", NULL },
+ { "/Keys/Open", NULL, get_key, 0, NULL, NULL },
+ { "/Keys/_Generate", NULL, gen_key, 0, NULL, NULL },
+ { "/Keys/Save Current", NULL, save_key, 0, NULL, NULL },
+
+ { "/Signing", NULL, NULL, 0, "<Branch>", NULL },
+ { "/Signing/Sign", NULL, really_sign_buffer, 0, NULL, NULL },
+
+ { "/_Help", NULL, NULL, 0, "<LastBranch>", NULL },
+ { "/Help/Help", NULL, show_help, 0, NULL, NULL },
+ { "/Help/sep1", NULL, NULL, 0, "<Separator>", NULL },
+ { "/Help/About", NULL, show_about, 0, NULL, NULL },
+ };
+
+ GtkAccelGroup* accel_group = gtk_accel_group_new();
+ GtkItemFactory* item_factory =
+ gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accel_group);
+ const gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
+ gtk_item_factory_create_items(item_factory, nmenu_items, menu_items, NULL);
+ gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
+ return gtk_item_factory_get_widget(item_factory, "<main>");
+ }
+
+int main(int argc, char *argv[])
+ {
+ gtk_init(&argc, &argv);
+
+ try {
+ Botan::LibraryInitializer init;
+
+ /* Create a new top-level window */
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(window), "DSA Utility");
+ gtk_signal_connect(GTK_OBJECT(window), "delete_event",
+ gtk_main_quit, NULL);
+
+ /* Create the vbox to hold our stuff */
+ GtkWidget* vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_border_width(GTK_CONTAINER(vbox), 1);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+
+ /* Create the menu bar */
+ GtkWidget *menubar = make_menubar(window);
+
+ /* Create the entry that holds the signature */
+ sig_view = gtk_entry_new();
+ gtk_editable_set_editable(GTK_EDITABLE(sig_view), FALSE);
+
+ /* Create the text box */
+ GtkWidget* view = gtk_text_view_new();
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
+
+ gtk_widget_set_size_request(view, 640, 480);
+ gtk_text_buffer_set_text(buffer, "Some initial contents.", -1);
+
+ // Resign it on each change: fast enough, but probably not really useful
+ //g_signal_connect(G_OBJECT(buffer), "changed", sign_buffer, 0);
+ g_signal_connect(G_OBJECT(buffer), "changed", zap_sig, 0);
+
+ gtk_container_add(GTK_CONTAINER(vbox), menubar);
+ gtk_container_add(GTK_CONTAINER(vbox), view);
+ gtk_container_add(GTK_CONTAINER(vbox), sig_view);
+
+ gtk_widget_show_all(window);
+
+ gtk_main();
+ }
+ catch(std::exception& e)
+ {
+ std::cout << e.what() << std::endl;
+ }
+ return 0;
+ }
diff --git a/misc/gtk/gtk_ui.cpp b/misc/gtk/gtk_ui.cpp
new file mode 100644
index 000000000..515fbc519
--- /dev/null
+++ b/misc/gtk/gtk_ui.cpp
@@ -0,0 +1,78 @@
+/*************************************************
+* GTK+ User Interface Source File *
+*************************************************/
+
+#include "gtk_ui.h"
+
+/*************************************************
+* GTK+ Callback *
+*************************************************/
+void GTK_UI::callback(GtkWidget* entry, gpointer passphrase_ptr)
+ {
+ const gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(entry));
+ char* passphrase = (char*)passphrase_ptr;
+ strcpy(passphrase, entry_text);
+ }
+
+/*************************************************
+* Get a passphrase from the user *
+*************************************************/
+std::string GTK_UI::get_passphrase(const std::string& what,
+ const std::string& source,
+ UI_Result& result) const
+ {
+ std::string msg = "A passphrase is needed to access the " + what;
+ if(source != "") msg += "\nin " + source;
+ return get_passphrase(msg, result);
+ }
+
+/*************************************************
+* Get a passphrase from the user *
+*************************************************/
+std::string GTK_UI::get_passphrase(const std::string& label_text,
+ UI_Result& result) const
+ {
+ const int MAX_PASSPHRASE = 64;
+
+ GtkDialogFlags flags =
+ (GtkDialogFlags)(GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL);
+
+ GtkWidget* dialog = gtk_dialog_new_with_buttons(
+ "Enter Passphrase",
+ NULL, flags,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ NULL);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+ GtkWidget* label = gtk_label_new(label_text.c_str());
+
+ GtkWidget* entry = gtk_entry_new();
+ gtk_entry_set_visibility(GTK_ENTRY(entry), 0);
+ gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+ gtk_entry_set_max_length(GTK_ENTRY(entry), MAX_PASSPHRASE);
+
+ char passphrase_buf[MAX_PASSPHRASE + 1] = { 0 };
+
+ gtk_signal_connect(GTK_OBJECT(entry), "activate",
+ GTK_SIGNAL_FUNC(callback), passphrase_buf);
+
+ GtkWidget* vbox = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, TRUE, TRUE, 0);
+ gtk_widget_show_all(vbox);
+
+ /* Block until we get something back */
+ result = CANCEL_ACTION;
+ if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
+ result = OK;
+
+ gtk_widget_destroy(dialog);
+
+ if(result == OK)
+ return std::string(passphrase_buf);
+ return "";
+ }
diff --git a/misc/gtk/gtk_ui.h b/misc/gtk/gtk_ui.h
new file mode 100644
index 000000000..9ced83a81
--- /dev/null
+++ b/misc/gtk/gtk_ui.h
@@ -0,0 +1,25 @@
+/*************************************************
+* GTK+ User Interface Header File *
+*************************************************/
+
+#ifndef BOTAN_EXT_GTK_UI__
+#define BOTAN_EXT_GTK_UI__
+
+#include <botan/ui.h>
+#include <gtk/gtk.h>
+
+/*************************************************
+* GTK+ Passphrase Callback Object *
+*************************************************/
+class GTK_UI : public Botan::User_Interface
+ {
+ public:
+ std::string get_passphrase(const std::string&, const std::string&,
+ UI_Result&) const;
+
+ std::string get_passphrase(const std::string&, UI_Result&) const;
+
+ static void callback(GtkWidget*, gpointer);
+ };
+
+#endif
diff --git a/misc/gtk/readme.txt b/misc/gtk/readme.txt
new file mode 100644
index 000000000..1f20732b6
--- /dev/null
+++ b/misc/gtk/readme.txt
@@ -0,0 +1,22 @@
+
+This is an example of how to use Botan in a GUI. You need at least Botan 1.3.8,
+and I would recommend 1.4.2.
+
+You'll also need GTK+ 2.x (I'm using 2.2, but AFAIK not any 2.2-specific
+functionality). Keep in mind that I was learning GTK as I was writing this, so
+it's not exactly the best GTK code you're likely to see.
+
+dsa.cpp is the main GTK+ driver. It has some comments at the top which point
+out major areas of interest.
+
+gtk_ui.* implement a User_Interface object that opens up a GTK+ dialog box that
+asks the user for their passphrase. It works pretty well, the only major
+deficiency is a fixed upper limit on the size of the passphrase (currently 64).
+You may want to use this in your own code, assuming you use GTK. If not, it
+should at least provide an outline for writing a version for your favorite
+windowing system.
+
+To build, you'll need to have GNU make, or be willing to compile it by hand.
+Just go get gmake, enough real stuff needs it anyway.
+
+Jack