// 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 #include #include #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& 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 details_strings_; GtkWidget* cert_combo_box_; GtkTextBuffer* cert_details_buffer_; ui::ScopedGObject::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& 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 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( 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, UTF8ToUTF16(x509_certificate_model::GetSubjectName(cert))); rv += "\n "; rv += l10n_util::GetStringFUTF8( IDS_CERT_SERIAL_NUMBER_FORMAT, 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 usages; x509_certificate_model::GetUsageStrings(cert, &usages); if (usages.size()) { rv += "\n "; rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT, 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, UTF8ToUTF16(key_usage_str)); } std::vector email_addresses; x509_certificate_model::GetEmailAddresses(cert, &email_addresses); if (email_addresses.size()) { rv += "\n "; rv += l10n_util::GetStringFUTF8( IDS_CERT_EMAIL_ADDRESSES_FORMAT, UTF8ToUTF16(JoinString(email_addresses, ','))); } rv += '\n'; rv += l10n_util::GetStringFUTF8( IDS_CERT_ISSUERNAME_FORMAT, UTF8ToUTF16(x509_certificate_model::GetIssuerName(cert))); base::string16 token(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& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); (new SSLClientCertificateSelector( contents, network_session, cert_request_info, callback))->Show(); } } // namespace chrome