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