Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / ssl_client_certificate_selector.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
6
7 #include <gtk/gtk.h>
8
9 #include <string>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/logging.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/certificate_viewer.h"
17 #include "chrome/browser/ssl/ssl_client_auth_observer.h"
18 #include "chrome/browser/ui/crypto_module_password_dialog_nss.h"
19 #include "chrome/browser/ui/gtk/constrained_window_gtk.h"
20 #include "chrome/browser/ui/gtk/gtk_util.h"
21 #include "chrome/common/net/x509_certificate_model.h"
22 #include "components/web_modal/web_contents_modal_dialog_manager.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "grit/generated_resources.h"
25 #include "net/cert/x509_certificate.h"
26 #include "net/ssl/ssl_cert_request_info.h"
27 #include "ui/base/gtk/gtk_hig_constants.h"
28 #include "ui/base/gtk/gtk_signal.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/gfx/gtk_compat.h"
31 #include "ui/gfx/native_widget_types.h"
32 #include "ui/gfx/scoped_gobject.h"
33
34 using content::BrowserThread;
35 using content::WebContents;
36 using web_modal::WebContentsModalDialogManager;
37
38 namespace {
39
40 enum {
41   RESPONSE_SHOW_CERT_INFO = 1,
42 };
43
44 ///////////////////////////////////////////////////////////////////////////////
45 // SSLClientCertificateSelector
46
47 class SSLClientCertificateSelector : public SSLClientAuthObserver {
48  public:
49   explicit SSLClientCertificateSelector(
50       WebContents* parent,
51       const net::HttpNetworkSession* network_session,
52       net::SSLCertRequestInfo* cert_request_info,
53       const base::Callback<void(net::X509Certificate*)>& callback);
54   virtual ~SSLClientCertificateSelector();
55
56   void Show();
57
58   // SSLClientAuthObserver implementation:
59   virtual void OnCertSelectedByNotification() OVERRIDE;
60
61  private:
62   void PopulateCerts();
63
64   net::X509Certificate* GetSelectedCert();
65
66   static std::string FormatComboBoxText(
67       net::X509Certificate::OSCertHandle cert,
68       const std::string& nickname);
69   static std::string FormatDetailsText(
70       net::X509Certificate::OSCertHandle cert);
71
72   // Callback after unlocking certificate slot.
73   void Unlocked();
74
75   CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnComboBoxChanged);
76   CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnViewClicked);
77   CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnCancelClicked);
78   CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnOkClicked);
79   CHROMEGTK_CALLBACK_1(SSLClientCertificateSelector, void, OnPromptShown,
80                        GtkWidget*);
81   CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnDestroy);
82
83   std::vector<std::string> details_strings_;
84
85   GtkWidget* cert_combo_box_;
86   GtkTextBuffer* cert_details_buffer_;
87
88   ui::ScopedGObject<GtkWidget>::Type root_widget_;
89   // Hold on to the select button to focus it.
90   GtkWidget* select_button_;
91
92   WebContents* web_contents_;
93   GtkWidget* window_;
94
95   DISALLOW_COPY_AND_ASSIGN(SSLClientCertificateSelector);
96 };
97
98 SSLClientCertificateSelector::SSLClientCertificateSelector(
99     WebContents* web_contents,
100     const net::HttpNetworkSession* network_session,
101     net::SSLCertRequestInfo* cert_request_info,
102     const base::Callback<void(net::X509Certificate*)>& callback)
103     : SSLClientAuthObserver(network_session, cert_request_info, callback),
104       web_contents_(web_contents),
105       window_(NULL) {
106   root_widget_.reset(gtk_vbox_new(FALSE, ui::kControlSpacing));
107   g_object_ref_sink(root_widget_.get());
108   g_signal_connect(root_widget_.get(),
109                    "destroy",
110                    G_CALLBACK(OnDestroyThunk),
111                    this);
112
113   GtkWidget* site_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
114   gtk_box_pack_start(GTK_BOX(root_widget_.get()), site_vbox,
115                      FALSE, FALSE, 0);
116
117   GtkWidget* site_description_label = gtk_util::CreateBoldLabel(
118       l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_SITE_DESCRIPTION_LABEL));
119   gtk_box_pack_start(GTK_BOX(site_vbox), site_description_label,
120                      FALSE, FALSE, 0);
121
122   GtkWidget* site_label = gtk_label_new(
123       cert_request_info->host_and_port.ToString().c_str());
124   gtk_util::LeftAlignMisc(site_label);
125   gtk_box_pack_start(GTK_BOX(site_vbox), site_label, FALSE, FALSE, 0);
126
127   GtkWidget* selector_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
128   gtk_box_pack_start(GTK_BOX(root_widget_.get()), selector_vbox,
129                      TRUE, TRUE, 0);
130
131   GtkWidget* choose_description_label = gtk_util::CreateBoldLabel(
132       l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CHOOSE_DESCRIPTION_LABEL));
133   gtk_box_pack_start(GTK_BOX(selector_vbox), choose_description_label,
134                      FALSE, FALSE, 0);
135
136
137   cert_combo_box_ = gtk_combo_box_new_text();
138   g_signal_connect(cert_combo_box_, "changed",
139                    G_CALLBACK(OnComboBoxChangedThunk), this);
140   gtk_box_pack_start(GTK_BOX(selector_vbox), cert_combo_box_,
141                      FALSE, FALSE, 0);
142
143   GtkWidget* details_label = gtk_label_new(l10n_util::GetStringUTF8(
144       IDS_CERT_SELECTOR_DETAILS_DESCRIPTION_LABEL).c_str());
145   gtk_util::LeftAlignMisc(details_label);
146   gtk_box_pack_start(GTK_BOX(selector_vbox), details_label, FALSE, FALSE, 0);
147
148   // TODO(mattm): fix text view coloring (should have grey background).
149   GtkWidget* cert_details_view = gtk_text_view_new();
150   gtk_text_view_set_editable(GTK_TEXT_VIEW(cert_details_view), FALSE);
151   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(cert_details_view), GTK_WRAP_WORD);
152   cert_details_buffer_ = gtk_text_view_get_buffer(
153       GTK_TEXT_VIEW(cert_details_view));
154   // We put the details in a frame instead of a scrolled window so that the
155   // entirety will be visible without requiring scrolling or expanding the
156   // dialog.  This does however mean the dialog will grow itself if you switch
157   // to different cert that has longer details text.
158   GtkWidget* details_frame = gtk_frame_new(NULL);
159   gtk_frame_set_shadow_type(GTK_FRAME(details_frame), GTK_SHADOW_ETCHED_IN);
160   gtk_container_add(GTK_CONTAINER(details_frame), cert_details_view);
161   gtk_box_pack_start(GTK_BOX(selector_vbox), details_frame, TRUE, TRUE, 0);
162
163   // And then create a set of buttons like a GtkDialog would.
164   GtkWidget* button_box = gtk_hbutton_box_new();
165   gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), GTK_BUTTONBOX_END);
166   gtk_box_set_spacing(GTK_BOX(button_box), ui::kControlSpacing);
167   gtk_box_pack_end(GTK_BOX(root_widget_.get()), button_box, FALSE, FALSE, 0);
168
169   GtkWidget* view_button = gtk_button_new_with_mnemonic(
170       l10n_util::GetStringUTF8(IDS_PAGEINFO_CERT_INFO_BUTTON).c_str());
171   gtk_box_pack_start(GTK_BOX(button_box), view_button, FALSE, FALSE, 0);
172   g_signal_connect(view_button, "clicked",
173                    G_CALLBACK(OnViewClickedThunk), this);
174
175   GtkWidget* cancel_button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
176   gtk_box_pack_end(GTK_BOX(button_box), cancel_button, FALSE, FALSE, 0);
177   g_signal_connect(cancel_button, "clicked",
178                    G_CALLBACK(OnCancelClickedThunk), this);
179
180   GtkWidget* select_button = gtk_button_new_from_stock(GTK_STOCK_OK);
181   gtk_box_pack_end(GTK_BOX(button_box), select_button, FALSE, FALSE, 0);
182   g_signal_connect(select_button, "clicked",
183                    G_CALLBACK(OnOkClickedThunk), this);
184
185   // When we are attached to a window, focus the select button.
186   select_button_ = select_button;
187   g_signal_connect(root_widget_.get(), "hierarchy-changed",
188                    G_CALLBACK(OnPromptShownThunk), this);
189   PopulateCerts();
190
191   gtk_widget_show_all(root_widget_.get());
192
193   StartObserving();
194 }
195
196 SSLClientCertificateSelector::~SSLClientCertificateSelector() {
197 }
198
199 void SSLClientCertificateSelector::Show() {
200   DCHECK(!window_);
201   window_ = CreateWebContentsModalDialogGtk(root_widget_.get(), select_button_);
202
203   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
204       WebContentsModalDialogManager::FromWebContents(web_contents_);
205   web_contents_modal_dialog_manager->ShowDialog(window_);
206 }
207
208 void SSLClientCertificateSelector::OnCertSelectedByNotification() {
209   DCHECK(window_);
210   gtk_widget_destroy(window_);
211 }
212
213 void SSLClientCertificateSelector::PopulateCerts() {
214   std::vector<std::string> nicknames;
215   x509_certificate_model::GetNicknameStringsFromCertList(
216       cert_request_info()->client_certs,
217       l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CERT_EXPIRED),
218       l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CERT_NOT_YET_VALID),
219       &nicknames);
220
221   DCHECK_EQ(nicknames.size(),
222             cert_request_info()->client_certs.size());
223
224   for (size_t i = 0; i < cert_request_info()->client_certs.size(); ++i) {
225     net::X509Certificate::OSCertHandle cert =
226         cert_request_info()->client_certs[i]->os_cert_handle();
227
228     details_strings_.push_back(FormatDetailsText(cert));
229
230     gtk_combo_box_append_text(
231         GTK_COMBO_BOX(cert_combo_box_),
232         FormatComboBoxText(cert, nicknames[i]).c_str());
233   }
234
235   // Auto-select the first cert.
236   gtk_combo_box_set_active(GTK_COMBO_BOX(cert_combo_box_), 0);
237 }
238
239 net::X509Certificate* SSLClientCertificateSelector::GetSelectedCert() {
240   int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(cert_combo_box_));
241   if (selected >= 0 &&
242       selected < static_cast<int>(
243           cert_request_info()->client_certs.size()))
244     return cert_request_info()->client_certs[selected].get();
245   return NULL;
246 }
247
248 // static
249 std::string SSLClientCertificateSelector::FormatComboBoxText(
250     net::X509Certificate::OSCertHandle cert, const std::string& nickname) {
251   std::string rv(nickname);
252   rv += " [";
253   rv += x509_certificate_model::GetSerialNumberHexified(cert, std::string());
254   rv += ']';
255   return rv;
256 }
257
258 // static
259 std::string SSLClientCertificateSelector::FormatDetailsText(
260     net::X509Certificate::OSCertHandle cert) {
261   std::string rv;
262
263   rv += l10n_util::GetStringFUTF8(
264       IDS_CERT_SUBJECTNAME_FORMAT,
265       base::UTF8ToUTF16(x509_certificate_model::GetSubjectName(cert)));
266
267   rv += "\n  ";
268   rv += l10n_util::GetStringFUTF8(
269       IDS_CERT_SERIAL_NUMBER_FORMAT,
270       base::UTF8ToUTF16(x509_certificate_model::GetSerialNumberHexified(
271           cert, std::string())));
272
273   base::Time issued, expires;
274   if (x509_certificate_model::GetTimes(cert, &issued, &expires)) {
275     base::string16 issued_str = base::TimeFormatShortDateAndTime(issued);
276     base::string16 expires_str = base::TimeFormatShortDateAndTime(expires);
277     rv += "\n  ";
278     rv += l10n_util::GetStringFUTF8(IDS_CERT_VALIDITY_RANGE_FORMAT,
279                                     issued_str, expires_str);
280   }
281
282   std::vector<std::string> usages;
283   x509_certificate_model::GetUsageStrings(cert, &usages);
284   if (usages.size()) {
285     rv += "\n  ";
286     rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT,
287                                     base::UTF8ToUTF16(JoinString(usages, ',')));
288   }
289
290   std::string key_usage_str = x509_certificate_model::GetKeyUsageString(cert);
291   if (!key_usage_str.empty()) {
292     rv += "\n  ";
293     rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_KEY_USAGE_FORMAT,
294                                     base::UTF8ToUTF16(key_usage_str));
295   }
296
297   std::vector<std::string> email_addresses;
298   x509_certificate_model::GetEmailAddresses(cert, &email_addresses);
299   if (email_addresses.size()) {
300     rv += "\n  ";
301     rv += l10n_util::GetStringFUTF8(
302         IDS_CERT_EMAIL_ADDRESSES_FORMAT,
303         base::UTF8ToUTF16(JoinString(email_addresses, ',')));
304   }
305
306   rv += '\n';
307   rv += l10n_util::GetStringFUTF8(
308       IDS_CERT_ISSUERNAME_FORMAT,
309       base::UTF8ToUTF16(x509_certificate_model::GetIssuerName(cert)));
310
311   base::string16 token(
312       base::UTF8ToUTF16(x509_certificate_model::GetTokenName(cert)));
313   if (!token.empty()) {
314     rv += '\n';
315     rv += l10n_util::GetStringFUTF8(IDS_CERT_TOKEN_FORMAT, token);
316   }
317
318   return rv;
319 }
320
321 void SSLClientCertificateSelector::Unlocked() {
322   // TODO(mattm): refactor so we don't need to call GetSelectedCert again.
323   net::X509Certificate* cert = GetSelectedCert();
324   CertificateSelected(cert);
325   DCHECK(window_);
326   gtk_widget_destroy(window_);
327 }
328
329 void SSLClientCertificateSelector::OnComboBoxChanged(GtkWidget* combo_box) {
330   int selected = gtk_combo_box_get_active(
331       GTK_COMBO_BOX(cert_combo_box_));
332   if (selected < 0)
333     return;
334   gtk_text_buffer_set_text(cert_details_buffer_,
335                            details_strings_[selected].c_str(),
336                            details_strings_[selected].size());
337 }
338
339 void SSLClientCertificateSelector::OnViewClicked(GtkWidget* button) {
340   net::X509Certificate* cert = GetSelectedCert();
341   if (cert) {
342     GtkWidget* toplevel = gtk_widget_get_toplevel(root_widget_.get());
343     ShowCertificateViewer(web_contents_, GTK_WINDOW(toplevel), cert);
344   }
345 }
346
347 void SSLClientCertificateSelector::OnCancelClicked(GtkWidget* button) {
348   CertificateSelected(NULL);
349   DCHECK(window_);
350   gtk_widget_destroy(window_);
351 }
352
353 void SSLClientCertificateSelector::OnOkClicked(GtkWidget* button) {
354   // Remove the observer before we try unlocking, otherwise we might act on a
355   // notification while waiting for the unlock dialog, causing us to delete
356   // ourself before the Unlocked callback gets called.
357   StopObserving();
358
359 #if defined(USE_NSS)
360   GtkWidget* toplevel = gtk_widget_get_toplevel(root_widget_.get());
361   net::X509Certificate* cert = GetSelectedCert();
362
363   chrome::UnlockCertSlotIfNecessary(
364       cert,
365       chrome::kCryptoModulePasswordClientAuth,
366       cert_request_info()->host_and_port,
367       GTK_WINDOW(toplevel),
368       base::Bind(&SSLClientCertificateSelector::Unlocked,
369                  base::Unretained(this)));
370 #else
371   Unlocked();
372 #endif
373 }
374
375 void SSLClientCertificateSelector::OnPromptShown(GtkWidget* widget,
376                                                  GtkWidget* previous_toplevel) {
377   if (!root_widget_.get() ||
378       !gtk_widget_is_toplevel(gtk_widget_get_toplevel(root_widget_.get())))
379     return;
380   gtk_widget_set_can_default(select_button_, TRUE);
381   gtk_widget_grab_default(select_button_);
382 }
383
384 void SSLClientCertificateSelector::OnDestroy(GtkWidget* widget) {
385   // The dialog was closed by escape key.
386   StopObserving();
387   CertificateSelected(NULL);
388   delete this;
389 }
390
391 }  // namespace
392
393 namespace chrome {
394
395 void ShowSSLClientCertificateSelector(
396     content::WebContents* contents,
397     const net::HttpNetworkSession* network_session,
398     net::SSLCertRequestInfo* cert_request_info,
399     const base::Callback<void(net::X509Certificate*)>& callback) {
400   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
401   (new SSLClientCertificateSelector(
402       contents, network_session, cert_request_info, callback))->Show();
403 }
404
405 }  // namespace chrome