1 | /* |
---|
2 | * Estonian ID card plugin for web browsers |
---|
3 | * |
---|
4 | * Copyright (C) 2010-2011 Codeborne <info@codeborne.com> |
---|
5 | * |
---|
6 | * This is free software; you can redistribute it and/or |
---|
7 | * modify it under the terms of the GNU Lesser General Public |
---|
8 | * License as published by the Free Software Foundation; either |
---|
9 | * version 2.1 of the License, or (at your option) any later version. |
---|
10 | * |
---|
11 | * This software is distributed in the hope that it will be useful, |
---|
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
14 | * Lesser General Public License for more details. |
---|
15 | * |
---|
16 | * You should have received a copy of the GNU Lesser General Public |
---|
17 | * License along with this library; if not, write to the Free Software |
---|
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
---|
19 | * |
---|
20 | */ |
---|
21 | |
---|
22 | #include <stdlib.h> |
---|
23 | #include <stdio.h> |
---|
24 | #include <string.h> |
---|
25 | #include <gtk/gtk.h> |
---|
26 | #include <gdk/gdkx.h> |
---|
27 | #include <gdk/gdkkeysyms.h> |
---|
28 | |
---|
29 | #include <gdk/gdk.h> |
---|
30 | #include "dialogs.h" |
---|
31 | #include "esteid_log.h" |
---|
32 | #include "l10n.h" |
---|
33 | #include "esteid_map.h" |
---|
34 | #include "esteid_certinfo.h" |
---|
35 | #include "esteid_time.h" |
---|
36 | #include "esteid_dialog_common.h" |
---|
37 | |
---|
38 | GtkWidget *dialog; |
---|
39 | GtkWidget* progressBar; |
---|
40 | int timeoutCounter; |
---|
41 | guint timerID; |
---|
42 | |
---|
43 | typedef struct { |
---|
44 | GtkWidget* button; |
---|
45 | unsigned minPin2Length; |
---|
46 | } EstEID_PIN2LengthControlData; |
---|
47 | |
---|
48 | void setDialogProperties(GtkWidget *dialog, GtkWidget *window) { |
---|
49 | gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); |
---|
50 | gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); |
---|
51 | gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); |
---|
52 | gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); |
---|
53 | gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); |
---|
54 | gtk_window_set_urgency_hint(GTK_WINDOW(dialog), TRUE); |
---|
55 | } |
---|
56 | |
---|
57 | static void enableButtonWhenPINLengthIsCorrect(GtkWidget* entry, EstEID_PIN2LengthControlData* pin2LengthControl) { |
---|
58 | gtk_widget_set_sensitive(GTK_WIDGET(pin2LengthControl->button), gtk_entry_get_text_length(GTK_ENTRY(entry)) >= pin2LengthControl->minPin2Length); |
---|
59 | } |
---|
60 | |
---|
61 | GtkWidget *createPIN2Dialog(GtkWidget *window, GtkWidget *entry, const char *nameAndID, const char *message, const unsigned minPin2Length) { |
---|
62 | dialog = gtk_dialog_new_with_buttons(createDialogTitle(nameAndID), GTK_WINDOW(window), |
---|
63 | GTK_DIALOG_DESTROY_WITH_PARENT, |
---|
64 | NULL); |
---|
65 | |
---|
66 | gtk_dialog_add_button((GtkDialog*) dialog, (const gchar*) l10n("Cancel"), GTK_RESPONSE_CANCEL); |
---|
67 | GtkWidget* signButton = gtk_dialog_add_button((GtkDialog*) dialog, (const gchar*) l10n("Sign"), GTK_RESPONSE_OK); |
---|
68 | gtk_widget_set_sensitive(signButton, FALSE); |
---|
69 | |
---|
70 | setDialogProperties(dialog, window); |
---|
71 | |
---|
72 | GtkWidget* vbox = gtk_vbox_new(FALSE, 12); |
---|
73 | |
---|
74 | if (message && message[0]) { |
---|
75 | GtkWidget *messageLabel = gtk_label_new(NULL); |
---|
76 | char *markup = g_markup_printf_escaped("<span color='red'>%s</span>", message); |
---|
77 | gtk_label_set_markup(GTK_LABEL(messageLabel), markup); |
---|
78 | free(markup); |
---|
79 | gtk_container_add(GTK_CONTAINER(vbox), messageLabel); |
---|
80 | } |
---|
81 | GtkWidget *nameLabel = gtk_label_new(nameAndID); |
---|
82 | gtk_container_add(GTK_CONTAINER(vbox), nameLabel); |
---|
83 | gtk_misc_set_alignment(GTK_MISC(nameLabel), 0.0, 0.0); |
---|
84 | |
---|
85 | GtkWidget* hbox = gtk_hbox_new(FALSE, 12); |
---|
86 | |
---|
87 | EstEID_PIN2LengthControlData* pin2LengthControl; |
---|
88 | pin2LengthControl = g_new0(EstEID_PIN2LengthControlData, 1); |
---|
89 | pin2LengthControl->button = signButton; |
---|
90 | pin2LengthControl->minPin2Length = minPin2Length; |
---|
91 | |
---|
92 | g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(enableButtonWhenPINLengthIsCorrect), (gpointer) pin2LengthControl); |
---|
93 | gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE); |
---|
94 | gtk_entry_set_max_length(GTK_ENTRY(entry), 12); |
---|
95 | gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); |
---|
96 | |
---|
97 | GtkWidget *label = gtk_label_new(l10n("For signing enter PIN2:")); |
---|
98 | gtk_label_set_use_underline(GTK_LABEL(label), TRUE); |
---|
99 | gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); |
---|
100 | |
---|
101 | gtk_container_add(GTK_CONTAINER(hbox), label); |
---|
102 | gtk_container_add(GTK_CONTAINER(hbox), entry); |
---|
103 | |
---|
104 | gtk_container_add(GTK_CONTAINER(vbox), hbox); |
---|
105 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); |
---|
106 | |
---|
107 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); |
---|
108 | gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); |
---|
109 | |
---|
110 | gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); |
---|
111 | |
---|
112 | g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(gtk_main_quit), NULL); |
---|
113 | |
---|
114 | gtk_widget_show_all(dialog); |
---|
115 | return dialog; |
---|
116 | } |
---|
117 | |
---|
118 | gint keyHandler(GtkWidget *window, GdkEventKey *key) { |
---|
119 | LOG_LOCATION; |
---|
120 | return TRUE; |
---|
121 | } |
---|
122 | |
---|
123 | static gboolean closingPreventionHandler(GtkWidget *window, GdkEvent *event, gpointer data) { |
---|
124 | LOG_LOCATION; |
---|
125 | EstEID_log("preventing to close widget %s", gtk_widget_get_name(GTK_WIDGET(window))); |
---|
126 | return TRUE; |
---|
127 | } |
---|
128 | |
---|
129 | GtkWidget *createPINPadDialog(GtkWidget *window, const char *nameAndID, const char *message) { |
---|
130 | LOG_LOCATION; |
---|
131 | dialog = gtk_dialog_new_with_buttons(createDialogTitle(nameAndID), GTK_WINDOW(window), |
---|
132 | GTK_DIALOG_DESTROY_WITH_PARENT, NULL); |
---|
133 | |
---|
134 | setDialogProperties(dialog, window); |
---|
135 | |
---|
136 | GtkWidget* vbox = gtk_vbox_new(FALSE, 12); |
---|
137 | |
---|
138 | if (message && message[0]) { |
---|
139 | GtkWidget* messageLabel = gtk_label_new(NULL); |
---|
140 | char* markup = g_markup_printf_escaped("<span color='red'>%s</span>", message); |
---|
141 | gtk_label_set_markup(GTK_LABEL(messageLabel), markup); |
---|
142 | free(markup); |
---|
143 | gtk_container_add(GTK_CONTAINER(vbox), messageLabel); |
---|
144 | } |
---|
145 | GtkWidget *nameLabel = gtk_label_new(nameAndID); |
---|
146 | gtk_container_add(GTK_CONTAINER(vbox), nameLabel); |
---|
147 | gtk_misc_set_alignment(GTK_MISC(nameLabel), 0.0, 0.0); |
---|
148 | |
---|
149 | GtkWidget* hbox = gtk_hbox_new(FALSE, 12); |
---|
150 | |
---|
151 | GtkWidget* label = gtk_label_new(l10n("For signing enter PIN2 from PIN pad")); |
---|
152 | gtk_label_set_use_underline(GTK_LABEL(label), TRUE); |
---|
153 | |
---|
154 | progressBar = gtk_progress_bar_new(); |
---|
155 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressBar), (gdouble) 1.0); |
---|
156 | |
---|
157 | gtk_container_add(GTK_CONTAINER(hbox), label); |
---|
158 | |
---|
159 | gtk_container_add(GTK_CONTAINER(vbox), hbox); |
---|
160 | gtk_container_add(GTK_CONTAINER(vbox), progressBar); |
---|
161 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); |
---|
162 | |
---|
163 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); |
---|
164 | gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); |
---|
165 | |
---|
166 | gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); |
---|
167 | |
---|
168 | g_signal_connect(G_OBJECT(dialog), "key_press_event", G_CALLBACK(keyHandler), NULL); |
---|
169 | g_signal_connect(G_OBJECT(dialog), "delete-event", G_CALLBACK(closingPreventionHandler), NULL); |
---|
170 | g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(gtk_main_quit), NULL); |
---|
171 | |
---|
172 | gtk_widget_show_all(dialog); |
---|
173 | return dialog; |
---|
174 | } |
---|
175 | |
---|
176 | GtkWidget *getGtkWindow(void *nativeWindowHandle) { |
---|
177 | GdkWindow *p = (GdkWindow *) gdk_window_lookup((XID) nativeWindowHandle); |
---|
178 | GtkWidget *window = NULL; |
---|
179 | gdk_window_get_user_data(p, (gpointer *) & window); |
---|
180 | return window; |
---|
181 | } |
---|
182 | |
---|
183 | void showAlert(void *nativeWindowHandle, const char *message) { |
---|
184 | GtkWidget *window = getGtkWindow(nativeWindowHandle); |
---|
185 | GtkWidget *alertDialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, message, ""); |
---|
186 | gtk_window_set_title(GTK_WINDOW(alertDialog), l10n("Error")); |
---|
187 | gtk_dialog_run(GTK_DIALOG(alertDialog)); |
---|
188 | gtk_widget_destroy(alertDialog); |
---|
189 | } |
---|
190 | |
---|
191 | void killCountdownTimer() { |
---|
192 | if (timerID) { |
---|
193 | if (g_source_remove(timerID)) { |
---|
194 | EstEID_log("killed timer %u", timerID); |
---|
195 | } |
---|
196 | else { |
---|
197 | EstEID_log("unable to kill timer %u (probably dead already)", timerID); |
---|
198 | } |
---|
199 | timerID = 0; |
---|
200 | } |
---|
201 | } |
---|
202 | |
---|
203 | static gboolean updateCountdownProgressBar() { |
---|
204 | --timeoutCounter; |
---|
205 | if (timeoutCounter <= 0) { |
---|
206 | EstEID_log("countdown reached 0, killing timer by returning FALSE from callback"); |
---|
207 | timerID = 0; |
---|
208 | return FALSE; |
---|
209 | } |
---|
210 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progressBar), (gdouble) timeoutCounter / (gdouble) 30); |
---|
211 | return TRUE; |
---|
212 | } |
---|
213 | |
---|
214 | char *promptForPIN(void *nativeWindowHandle, const char *name, const char *message, unsigned minPin2Length, int usePinPad) { |
---|
215 | LOG_LOCATION; |
---|
216 | GtkWidget *window = getGtkWindow(nativeWindowHandle); |
---|
217 | GtkWidget *entry = gtk_entry_new(); |
---|
218 | char *pin; |
---|
219 | |
---|
220 | if (!usePinPad) { |
---|
221 | // Simple card reader |
---|
222 | dialog = createPIN2Dialog(window, entry, name, message, minPin2Length); |
---|
223 | |
---|
224 | gint result = gtk_dialog_run(GTK_DIALOG(dialog)); |
---|
225 | gtk_widget_hide(dialog); |
---|
226 | if (result == GTK_RESPONSE_OK) { |
---|
227 | pin = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); |
---|
228 | EstEID_log("promptForPIN\t got pin"); |
---|
229 | } |
---|
230 | else { |
---|
231 | pin = strdup(""); |
---|
232 | EstEID_log("promptForPIN\t user canceled"); |
---|
233 | } |
---|
234 | } |
---|
235 | else { |
---|
236 | // PIN pad |
---|
237 | killCountdownTimer(); |
---|
238 | timeoutCounter = 30; |
---|
239 | EstEID_log("timeoutCounter value set to %i", timeoutCounter); |
---|
240 | dialog = createPINPadDialog(window, name, message); |
---|
241 | g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(updateCountdownProgressBar), NULL); |
---|
242 | timerID = g_timeout_add_seconds(1, (GSourceFunc) updateCountdownProgressBar, NULL); |
---|
243 | EstEID_log("created timer %u", timerID); |
---|
244 | gtk_dialog_run(GTK_DIALOG(dialog)); |
---|
245 | pin = strdup(""); |
---|
246 | } |
---|
247 | |
---|
248 | return pin; |
---|
249 | } |
---|
250 | |
---|
251 | static GtkTreeModel* createAndFillModel() { |
---|
252 | GtkListStore *certificatesList = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); |
---|
253 | GtkTreeIter iterator; |
---|
254 | char *validTo = NULL; |
---|
255 | |
---|
256 | EstEID_Certs *certs = EstEID_loadCerts(); |
---|
257 | EstEID_log("%i certificates found", certs->count); |
---|
258 | for (int i = 0; i < certs->count; i++) { |
---|
259 | EstEID_Map cert = certs->certs[i]; |
---|
260 | if (!EstEID_mapGet(cert, "usageNonRepudiation")) continue; |
---|
261 | |
---|
262 | validTo = getDateFromDateTime(EstEID_mapGet(cert, "validTo")); |
---|
263 | |
---|
264 | gtk_list_store_append(certificatesList, &iterator); |
---|
265 | gtk_list_store_set(certificatesList, &iterator, |
---|
266 | 0, EstEID_mapGet(cert, "commonName"), |
---|
267 | 1, EstEID_mapGet(cert, "organizationName"), |
---|
268 | 2, validTo, |
---|
269 | 3, EstEID_mapGet(cert, "certHash"), |
---|
270 | -1); |
---|
271 | if (validTo) { |
---|
272 | free(validTo); |
---|
273 | validTo = NULL; |
---|
274 | } |
---|
275 | } |
---|
276 | return GTK_TREE_MODEL(certificatesList); |
---|
277 | } |
---|
278 | |
---|
279 | static void selectionFunction(GtkTreeSelection* selection, GtkDialog* dialog) { |
---|
280 | gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_OK, gtk_tree_selection_count_selected_rows(selection) > 0); |
---|
281 | } |
---|
282 | |
---|
283 | static void doubleClick(GtkTreeView* treeView, GtkTreePath* path, GtkTreeViewColumn *col, GtkDialog* dialog) { |
---|
284 | gtk_dialog_response(dialog, GTK_RESPONSE_OK); |
---|
285 | } |
---|
286 | |
---|
287 | GtkWidget *createCertificateSelectionView(GtkWidget *window) { |
---|
288 | GtkWidget* certificatesView = gtk_tree_view_new(); |
---|
289 | |
---|
290 | gtk_widget_set_size_request(certificatesView, 400, 200); |
---|
291 | |
---|
292 | GtkCellRenderer* renderer; |
---|
293 | |
---|
294 | renderer = gtk_cell_renderer_text_new(); |
---|
295 | |
---|
296 | GtkTreeViewColumn* col1 = gtk_tree_view_column_new_with_attributes(l10n("Certificate"), renderer, "text", 0, NULL); |
---|
297 | col1->expand = TRUE; |
---|
298 | gtk_tree_view_append_column(GTK_TREE_VIEW(certificatesView), col1); |
---|
299 | |
---|
300 | renderer = gtk_cell_renderer_text_new(); |
---|
301 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(certificatesView), -1, l10n("Type"), renderer, "text", 1, NULL); |
---|
302 | |
---|
303 | renderer = gtk_cell_renderer_text_new(); |
---|
304 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(certificatesView), -1, l10n("Valid to"), renderer, "text", 2, NULL); |
---|
305 | |
---|
306 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(certificatesView), TRUE); |
---|
307 | GtkTreeModel* model = createAndFillModel(); |
---|
308 | |
---|
309 | gtk_tree_view_set_model(GTK_TREE_VIEW(certificatesView), model); |
---|
310 | |
---|
311 | GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(certificatesView)); |
---|
312 | GtkTreeIter iter; |
---|
313 | if (gtk_tree_model_get_iter_first(model, &iter)) { |
---|
314 | gtk_tree_selection_select_iter(selection, &iter); |
---|
315 | selectionFunction(selection, GTK_DIALOG(window)); |
---|
316 | } |
---|
317 | |
---|
318 | g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selectionFunction), GTK_DIALOG(window)); |
---|
319 | g_signal_connect(G_OBJECT(certificatesView), "row-activated", G_CALLBACK(doubleClick), GTK_DIALOG(window)); |
---|
320 | |
---|
321 | return certificatesView; |
---|
322 | } |
---|
323 | |
---|
324 | int promptForCertificate(void *nativeWindowHandle, char* certId) { |
---|
325 | certId[0] = '\0'; |
---|
326 | |
---|
327 | GtkWidget *window = getGtkWindow(nativeWindowHandle); |
---|
328 | dialog = gtk_dialog_new_with_buttons(l10n("Select certificate"), GTK_WINDOW(window), // ToDo l10n |
---|
329 | GTK_DIALOG_DESTROY_WITH_PARENT, |
---|
330 | NULL); |
---|
331 | |
---|
332 | gtk_dialog_add_button((GtkDialog*) dialog, (const gchar*) l10n("Cancel"), GTK_RESPONSE_CANCEL); |
---|
333 | gtk_dialog_add_button((GtkDialog*) dialog, (const gchar*) l10n("Select"), GTK_RESPONSE_OK); |
---|
334 | |
---|
335 | gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, FALSE); |
---|
336 | |
---|
337 | GtkWidget *warningLabel = gtk_label_new((const gchar*) l10n("By selecting a certificate I accept that my name and personal ID code will be sent to service provider.")); |
---|
338 | GtkWidget* vbox = gtk_vbox_new(FALSE, 12); |
---|
339 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); |
---|
340 | gtk_container_add(GTK_CONTAINER(vbox), warningLabel); |
---|
341 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox); |
---|
342 | |
---|
343 | GtkWidget* certificatesView = createCertificateSelectionView(dialog); |
---|
344 | |
---|
345 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), certificatesView); |
---|
346 | |
---|
347 | setDialogProperties(dialog, window); |
---|
348 | gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); |
---|
349 | gtk_widget_show_all(dialog); |
---|
350 | |
---|
351 | gint result = gtk_dialog_run(GTK_DIALOG(dialog)); |
---|
352 | |
---|
353 | gtk_widget_hide(dialog); |
---|
354 | |
---|
355 | int returnValue = IDCANCEL; |
---|
356 | if (result == GTK_RESPONSE_OK) { |
---|
357 | GtkTreeSelection* selectedRow = gtk_tree_view_get_selection(GTK_TREE_VIEW(certificatesView)); |
---|
358 | GtkTreeModel* model = NULL; |
---|
359 | GtkTreeIter iter; |
---|
360 | if (gtk_tree_selection_get_selected(selectedRow, &model, &iter)) { |
---|
361 | gchar* id; |
---|
362 | gtk_tree_model_get(model, &iter, 3, &id, -1); |
---|
363 | strcpy(certId, id); |
---|
364 | g_free(id); |
---|
365 | returnValue = IDOK; |
---|
366 | EstEID_log("promptForCertificate dialog returned cert ID %s", certId); |
---|
367 | } |
---|
368 | else { |
---|
369 | EstEID_log("promptForCertificate dialog returned without cert selection"); |
---|
370 | } |
---|
371 | } |
---|
372 | else { |
---|
373 | EstEID_log("promptForCertificate dialog canceled by user"); |
---|
374 | } |
---|
375 | gtk_widget_destroy(dialog); |
---|
376 | return returnValue; |
---|
377 | } |
---|
378 | |
---|
379 | void closePinPadModalSheet() { |
---|
380 | LOG_LOCATION; |
---|
381 | killCountdownTimer(); |
---|
382 | gtk_widget_destroy(dialog); |
---|
383 | } |
---|