root/chrome/browser/ui/gtk/ssl_client_certificate_selector.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. window_
  2. Show
  3. OnCertSelectedByNotification
  4. PopulateCerts
  5. GetSelectedCert
  6. FormatComboBoxText
  7. FormatDetailsText
  8. Unlocked
  9. OnComboBoxChanged
  10. OnViewClicked
  11. OnCancelClicked
  12. OnOkClicked
  13. OnPromptShown
  14. OnDestroy
  15. ShowSSLClientCertificateSelector

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ssl/ssl_client_certificate_selector.h"

#include <gtk/gtk.h>

#include <string>
#include <vector>

#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/certificate_viewer.h"
#include "chrome/browser/ssl/ssl_client_auth_observer.h"
#include "chrome/browser/ui/crypto_module_password_dialog_nss.h"
#include "chrome/browser/ui/gtk/constrained_window_gtk.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/common/net/x509_certificate_model.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/browser_thread.h"
#include "grit/generated_resources.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/gtk/gtk_signal.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/gtk_compat.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/scoped_gobject.h"

using content::BrowserThread;
using content::WebContents;
using web_modal::WebContentsModalDialogManager;

namespace {

enum {
  RESPONSE_SHOW_CERT_INFO = 1,
};

///////////////////////////////////////////////////////////////////////////////
// SSLClientCertificateSelector

class SSLClientCertificateSelector : public SSLClientAuthObserver {
 public:
  explicit SSLClientCertificateSelector(
      WebContents* parent,
      const net::HttpNetworkSession* network_session,
      net::SSLCertRequestInfo* cert_request_info,
      const base::Callback<void(net::X509Certificate*)>& callback);
  virtual ~SSLClientCertificateSelector();

  void Show();

  // SSLClientAuthObserver implementation:
  virtual void OnCertSelectedByNotification() OVERRIDE;

 private:
  void PopulateCerts();

  net::X509Certificate* GetSelectedCert();

  static std::string FormatComboBoxText(
      net::X509Certificate::OSCertHandle cert,
      const std::string& nickname);
  static std::string FormatDetailsText(
      net::X509Certificate::OSCertHandle cert);

  // Callback after unlocking certificate slot.
  void Unlocked();

  CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnComboBoxChanged);
  CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnViewClicked);
  CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnCancelClicked);
  CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnOkClicked);
  CHROMEGTK_CALLBACK_1(SSLClientCertificateSelector, void, OnPromptShown,
                       GtkWidget*);
  CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector, void, OnDestroy);

  std::vector<std::string> details_strings_;

  GtkWidget* cert_combo_box_;
  GtkTextBuffer* cert_details_buffer_;

  ui::ScopedGObject<GtkWidget>::Type root_widget_;
  // Hold on to the select button to focus it.
  GtkWidget* select_button_;

  WebContents* web_contents_;
  GtkWidget* window_;

  DISALLOW_COPY_AND_ASSIGN(SSLClientCertificateSelector);
};

SSLClientCertificateSelector::SSLClientCertificateSelector(
    WebContents* web_contents,
    const net::HttpNetworkSession* network_session,
    net::SSLCertRequestInfo* cert_request_info,
    const base::Callback<void(net::X509Certificate*)>& callback)
    : SSLClientAuthObserver(network_session, cert_request_info, callback),
      web_contents_(web_contents),
      window_(NULL) {
  root_widget_.reset(gtk_vbox_new(FALSE, ui::kControlSpacing));
  g_object_ref_sink(root_widget_.get());
  g_signal_connect(root_widget_.get(),
                   "destroy",
                   G_CALLBACK(OnDestroyThunk),
                   this);

  GtkWidget* site_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
  gtk_box_pack_start(GTK_BOX(root_widget_.get()), site_vbox,
                     FALSE, FALSE, 0);

  GtkWidget* site_description_label = gtk_util::CreateBoldLabel(
      l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_SITE_DESCRIPTION_LABEL));
  gtk_box_pack_start(GTK_BOX(site_vbox), site_description_label,
                     FALSE, FALSE, 0);

  GtkWidget* site_label = gtk_label_new(
      cert_request_info->host_and_port.ToString().c_str());
  gtk_util::LeftAlignMisc(site_label);
  gtk_box_pack_start(GTK_BOX(site_vbox), site_label, FALSE, FALSE, 0);

  GtkWidget* selector_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
  gtk_box_pack_start(GTK_BOX(root_widget_.get()), selector_vbox,
                     TRUE, TRUE, 0);

  GtkWidget* choose_description_label = gtk_util::CreateBoldLabel(
      l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CHOOSE_DESCRIPTION_LABEL));
  gtk_box_pack_start(GTK_BOX(selector_vbox), choose_description_label,
                     FALSE, FALSE, 0);


  cert_combo_box_ = gtk_combo_box_new_text();
  g_signal_connect(cert_combo_box_, "changed",
                   G_CALLBACK(OnComboBoxChangedThunk), this);
  gtk_box_pack_start(GTK_BOX(selector_vbox), cert_combo_box_,
                     FALSE, FALSE, 0);

  GtkWidget* details_label = gtk_label_new(l10n_util::GetStringUTF8(
      IDS_CERT_SELECTOR_DETAILS_DESCRIPTION_LABEL).c_str());
  gtk_util::LeftAlignMisc(details_label);
  gtk_box_pack_start(GTK_BOX(selector_vbox), details_label, FALSE, FALSE, 0);

  // TODO(mattm): fix text view coloring (should have grey background).
  GtkWidget* cert_details_view = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(cert_details_view), FALSE);
  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(cert_details_view), GTK_WRAP_WORD);
  cert_details_buffer_ = gtk_text_view_get_buffer(
      GTK_TEXT_VIEW(cert_details_view));
  // We put the details in a frame instead of a scrolled window so that the
  // entirety will be visible without requiring scrolling or expanding the
  // dialog.  This does however mean the dialog will grow itself if you switch
  // to different cert that has longer details text.
  GtkWidget* details_frame = gtk_frame_new(NULL);
  gtk_frame_set_shadow_type(GTK_FRAME(details_frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_add(GTK_CONTAINER(details_frame), cert_details_view);
  gtk_box_pack_start(GTK_BOX(selector_vbox), details_frame, TRUE, TRUE, 0);

  // And then create a set of buttons like a GtkDialog would.
  GtkWidget* button_box = gtk_hbutton_box_new();
  gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), GTK_BUTTONBOX_END);
  gtk_box_set_spacing(GTK_BOX(button_box), ui::kControlSpacing);
  gtk_box_pack_end(GTK_BOX(root_widget_.get()), button_box, FALSE, FALSE, 0);

  GtkWidget* view_button = gtk_button_new_with_mnemonic(
      l10n_util::GetStringUTF8(IDS_PAGEINFO_CERT_INFO_BUTTON).c_str());
  gtk_box_pack_start(GTK_BOX(button_box), view_button, FALSE, FALSE, 0);
  g_signal_connect(view_button, "clicked",
                   G_CALLBACK(OnViewClickedThunk), this);

  GtkWidget* cancel_button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
  gtk_box_pack_end(GTK_BOX(button_box), cancel_button, FALSE, FALSE, 0);
  g_signal_connect(cancel_button, "clicked",
                   G_CALLBACK(OnCancelClickedThunk), this);

  GtkWidget* select_button = gtk_button_new_from_stock(GTK_STOCK_OK);
  gtk_box_pack_end(GTK_BOX(button_box), select_button, FALSE, FALSE, 0);
  g_signal_connect(select_button, "clicked",
                   G_CALLBACK(OnOkClickedThunk), this);

  // When we are attached to a window, focus the select button.
  select_button_ = select_button;
  g_signal_connect(root_widget_.get(), "hierarchy-changed",
                   G_CALLBACK(OnPromptShownThunk), this);
  PopulateCerts();

  gtk_widget_show_all(root_widget_.get());

  StartObserving();
}

SSLClientCertificateSelector::~SSLClientCertificateSelector() {
}

void SSLClientCertificateSelector::Show() {
  DCHECK(!window_);
  window_ = CreateWebContentsModalDialogGtk(root_widget_.get(), select_button_);

  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
      WebContentsModalDialogManager::FromWebContents(web_contents_);
  web_contents_modal_dialog_manager->ShowDialog(window_);
}

void SSLClientCertificateSelector::OnCertSelectedByNotification() {
  DCHECK(window_);
  gtk_widget_destroy(window_);
}

void SSLClientCertificateSelector::PopulateCerts() {
  std::vector<std::string> nicknames;
  x509_certificate_model::GetNicknameStringsFromCertList(
      cert_request_info()->client_certs,
      l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CERT_EXPIRED),
      l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CERT_NOT_YET_VALID),
      &nicknames);

  DCHECK_EQ(nicknames.size(),
            cert_request_info()->client_certs.size());

  for (size_t i = 0; i < cert_request_info()->client_certs.size(); ++i) {
    net::X509Certificate::OSCertHandle cert =
        cert_request_info()->client_certs[i]->os_cert_handle();

    details_strings_.push_back(FormatDetailsText(cert));

    gtk_combo_box_append_text(
        GTK_COMBO_BOX(cert_combo_box_),
        FormatComboBoxText(cert, nicknames[i]).c_str());
  }

  // Auto-select the first cert.
  gtk_combo_box_set_active(GTK_COMBO_BOX(cert_combo_box_), 0);
}

net::X509Certificate* SSLClientCertificateSelector::GetSelectedCert() {
  int selected = gtk_combo_box_get_active(GTK_COMBO_BOX(cert_combo_box_));
  if (selected >= 0 &&
      selected < static_cast<int>(
          cert_request_info()->client_certs.size()))
    return cert_request_info()->client_certs[selected].get();
  return NULL;
}

// static
std::string SSLClientCertificateSelector::FormatComboBoxText(
    net::X509Certificate::OSCertHandle cert, const std::string& nickname) {
  std::string rv(nickname);
  rv += " [";
  rv += x509_certificate_model::GetSerialNumberHexified(cert, std::string());
  rv += ']';
  return rv;
}

// static
std::string SSLClientCertificateSelector::FormatDetailsText(
    net::X509Certificate::OSCertHandle cert) {
  std::string rv;

  rv += l10n_util::GetStringFUTF8(
      IDS_CERT_SUBJECTNAME_FORMAT,
      base::UTF8ToUTF16(x509_certificate_model::GetSubjectName(cert)));

  rv += "\n  ";
  rv += l10n_util::GetStringFUTF8(
      IDS_CERT_SERIAL_NUMBER_FORMAT,
      base::UTF8ToUTF16(x509_certificate_model::GetSerialNumberHexified(
          cert, std::string())));

  base::Time issued, expires;
  if (x509_certificate_model::GetTimes(cert, &issued, &expires)) {
    base::string16 issued_str = base::TimeFormatShortDateAndTime(issued);
    base::string16 expires_str = base::TimeFormatShortDateAndTime(expires);
    rv += "\n  ";
    rv += l10n_util::GetStringFUTF8(IDS_CERT_VALIDITY_RANGE_FORMAT,
                                    issued_str, expires_str);
  }

  std::vector<std::string> usages;
  x509_certificate_model::GetUsageStrings(cert, &usages);
  if (usages.size()) {
    rv += "\n  ";
    rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT,
                                    base::UTF8ToUTF16(JoinString(usages, ',')));
  }

  std::string key_usage_str = x509_certificate_model::GetKeyUsageString(cert);
  if (!key_usage_str.empty()) {
    rv += "\n  ";
    rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_KEY_USAGE_FORMAT,
                                    base::UTF8ToUTF16(key_usage_str));
  }

  std::vector<std::string> email_addresses;
  x509_certificate_model::GetEmailAddresses(cert, &email_addresses);
  if (email_addresses.size()) {
    rv += "\n  ";
    rv += l10n_util::GetStringFUTF8(
        IDS_CERT_EMAIL_ADDRESSES_FORMAT,
        base::UTF8ToUTF16(JoinString(email_addresses, ',')));
  }

  rv += '\n';
  rv += l10n_util::GetStringFUTF8(
      IDS_CERT_ISSUERNAME_FORMAT,
      base::UTF8ToUTF16(x509_certificate_model::GetIssuerName(cert)));

  base::string16 token(
      base::UTF8ToUTF16(x509_certificate_model::GetTokenName(cert)));
  if (!token.empty()) {
    rv += '\n';
    rv += l10n_util::GetStringFUTF8(IDS_CERT_TOKEN_FORMAT, token);
  }

  return rv;
}

void SSLClientCertificateSelector::Unlocked() {
  // TODO(mattm): refactor so we don't need to call GetSelectedCert again.
  net::X509Certificate* cert = GetSelectedCert();
  CertificateSelected(cert);
  DCHECK(window_);
  gtk_widget_destroy(window_);
}

void SSLClientCertificateSelector::OnComboBoxChanged(GtkWidget* combo_box) {
  int selected = gtk_combo_box_get_active(
      GTK_COMBO_BOX(cert_combo_box_));
  if (selected < 0)
    return;
  gtk_text_buffer_set_text(cert_details_buffer_,
                           details_strings_[selected].c_str(),
                           details_strings_[selected].size());
}

void SSLClientCertificateSelector::OnViewClicked(GtkWidget* button) {
  net::X509Certificate* cert = GetSelectedCert();
  if (cert) {
    GtkWidget* toplevel = gtk_widget_get_toplevel(root_widget_.get());
    ShowCertificateViewer(web_contents_, GTK_WINDOW(toplevel), cert);
  }
}

void SSLClientCertificateSelector::OnCancelClicked(GtkWidget* button) {
  CertificateSelected(NULL);
  DCHECK(window_);
  gtk_widget_destroy(window_);
}

void SSLClientCertificateSelector::OnOkClicked(GtkWidget* button) {
  // Remove the observer before we try unlocking, otherwise we might act on a
  // notification while waiting for the unlock dialog, causing us to delete
  // ourself before the Unlocked callback gets called.
  StopObserving();

#if defined(USE_NSS)
  GtkWidget* toplevel = gtk_widget_get_toplevel(root_widget_.get());
  net::X509Certificate* cert = GetSelectedCert();

  chrome::UnlockCertSlotIfNecessary(
      cert,
      chrome::kCryptoModulePasswordClientAuth,
      cert_request_info()->host_and_port,
      GTK_WINDOW(toplevel),
      base::Bind(&SSLClientCertificateSelector::Unlocked,
                 base::Unretained(this)));
#else
  Unlocked();
#endif
}

void SSLClientCertificateSelector::OnPromptShown(GtkWidget* widget,
                                                 GtkWidget* previous_toplevel) {
  if (!root_widget_.get() ||
      !gtk_widget_is_toplevel(gtk_widget_get_toplevel(root_widget_.get())))
    return;
  gtk_widget_set_can_default(select_button_, TRUE);
  gtk_widget_grab_default(select_button_);
}

void SSLClientCertificateSelector::OnDestroy(GtkWidget* widget) {
  // The dialog was closed by escape key.
  StopObserving();
  CertificateSelected(NULL);
  delete this;
}

}  // namespace

namespace chrome {

void ShowSSLClientCertificateSelector(
    content::WebContents* contents,
    const net::HttpNetworkSession* network_session,
    net::SSLCertRequestInfo* cert_request_info,
    const base::Callback<void(net::X509Certificate*)>& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  (new SSLClientCertificateSelector(
      contents, network_session, cert_request_info, callback))->Show();
}

}  // namespace chrome

/* [<][>][^][v][top][bottom][index][help] */