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
30namespace {
31
32enum {
33  RESPONSE_SHOW_CERT_INFO = 1,
34};
35
36///////////////////////////////////////////////////////////////////////////////
37// SSLClientCertificateSelector
38
39class 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:
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
98SSLClientCertificateSelector::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
192SSLClientCertificateSelector::~SSLClientCertificateSelector() {
193  root_widget_.Destroy();
194}
195
196void SSLClientCertificateSelector::Show() {
197  DCHECK(!window_);
198  window_ = parent_->CreateConstrainedDialog(this);
199}
200
201void SSLClientCertificateSelector::OnCertSelectedByNotification() {
202  delegate_ = NULL;
203  DCHECK(window_);
204  window_->CloseConstrainedWindow();
205}
206
207GtkWidget* SSLClientCertificateSelector::GetFocusWidget() {
208  return select_button_;
209}
210
211void 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
220void 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
246net::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
256std::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
266std::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
327void 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
336void 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
346void 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
354void SSLClientCertificateSelector::OnCancelClicked(GtkWidget* button) {
355  StopObserving();
356  delegate_->CertificateSelected(NULL);
357  delegate_ = NULL;
358  DCHECK(window_);
359  window_->CloseConstrainedWindow();
360}
361
362void 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
377void 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
391namespace browser {
392
393void 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