diff options
author | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-04 01:46:57 +0000 |
---|---|---|
committer | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-04 01:46:57 +0000 |
commit | b1f18494233f64c720729039222d9ad66c8ae95f (patch) | |
tree | 0f2848bdedba2a46c3d3b3fc8c7855d2786fd14d /chrome/browser/ssl | |
parent | b1995975f4e6b17df513a6d0dae44b37c46c5e00 (diff) | |
download | chromium_src-b1f18494233f64c720729039222d9ad66c8ae95f.zip chromium_src-b1f18494233f64c720729039222d9ad66c8ae95f.tar.gz chromium_src-b1f18494233f64c720729039222d9ad66c8ae95f.tar.bz2 |
Linux: implement Client SSL Certificate selection UI
BUG=25241
TEST=Visit site for which you have a client certificate, verify that dialog comes up and you can select your cert (or cancel)
Review URL: http://codereview.chromium.org/661241
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40587 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ssl')
-rw-r--r-- | chrome/browser/ssl/ssl_client_auth_handler.h | 10 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_client_auth_handler_gtk.cc | 322 |
2 files changed, 325 insertions, 7 deletions
diff --git a/chrome/browser/ssl/ssl_client_auth_handler.h b/chrome/browser/ssl/ssl_client_auth_handler.h index 537b0ea..f63d4c9 100644 --- a/chrome/browser/ssl/ssl_client_auth_handler.h +++ b/chrome/browser/ssl/ssl_client_auth_handler.h @@ -33,6 +33,11 @@ class SSLClientAuthHandler // Should only be called on the IO thread. void OnRequestCancelled(); + // Calls DoCertificateSelected on the I/O thread. + // Called on the UI thread after the user has made a selection (which may + // be long after DoSelectCertificate returns, if the UI is modeless/async.) + void CertificateSelected(net::X509Certificate* cert); + private: friend class base::RefCountedThreadSafe<SSLClientAuthHandler>; @@ -42,11 +47,6 @@ class SSLClientAuthHandler // Called on the UI thread. void DoSelectCertificate(); - // Calls DoCertificateSelected on the I/O thread. - // Called on the UI thread after the user has made a selection (which may - // be long after DoSelectCertificate returns, if the UI is modeless/async.) - void CertificateSelected(net::X509Certificate* cert); - // Notifies that the user has selected a cert. // Called on the IO thread. void DoCertificateSelected(net::X509Certificate* cert); diff --git a/chrome/browser/ssl/ssl_client_auth_handler_gtk.cc b/chrome/browser/ssl/ssl_client_auth_handler_gtk.cc index 7ea1aec..49fa4c5 100644 --- a/chrome/browser/ssl/ssl_client_auth_handler_gtk.cc +++ b/chrome/browser/ssl/ssl_client_auth_handler_gtk.cc @@ -3,10 +3,328 @@ // found in the LICENSE file. #include "chrome/browser/ssl/ssl_client_auth_handler.h" + +#include <cert.h> +#include <gtk/gtk.h> + +#include <string> +#include <vector> + +#include "app/gfx/native_widget_types.h" +#include "app/l10n_util.h" +#include "base/i18n/time_formatting.h" #include "base/logging.h" +#include "base/nss_util.h" +#include "chrome/browser/gtk/certificate_viewer.h" +#include "chrome/browser/gtk/gtk_util.h" +#include "chrome/third_party/mozilla_security_manager/nsNSSCertHelper.h" +#include "chrome/third_party/mozilla_security_manager/nsNSSCertificate.h" +#include "chrome/third_party/mozilla_security_manager/nsUsageArrayHelper.h" +#include "grit/generated_resources.h" #include "net/base/x509_certificate.h" +// PSM = Mozilla's Personal Security Manager. +namespace psm = mozilla_security_manager; + +namespace { + +enum { + RESPONSE_SHOW_CERT_INFO = 1, +}; + + +/////////////////////////////////////////////////////////////////////////////// +// SSLClientCertificateSelector + +class SSLClientCertificateSelector { + public: + SSLClientCertificateSelector(gfx::NativeWindow parent, + net::SSLCertRequestInfo* cert_request_info, + SSLClientAuthHandler* delegate); + + void Show(); + + private: + void PopulateCerts(); + + static std::string FormatComboBoxText(CERTCertificate* cert, + const char* nickname); + static std::string FormatDetailsText(CERTCertificate* cert); + + static void OnComboBoxChanged(GtkComboBox* combo_box, + SSLClientCertificateSelector* cert_selector); + static void OnResponse(GtkDialog* dialog, gint response_id, + SSLClientCertificateSelector* cert_selector); + static void OnDestroy(GtkDialog* dialog, + SSLClientCertificateSelector* cert_selector); + + SSLClientAuthHandler* delegate_; + scoped_refptr<net::SSLCertRequestInfo> cert_request_info_; + + std::vector<std::string> details_strings_; + + GtkWidget* dialog_; + GtkWidget* cert_combo_box_; + GtkTextBuffer* cert_details_buffer_; +}; + +SSLClientCertificateSelector::SSLClientCertificateSelector( + gfx::NativeWindow parent, + net::SSLCertRequestInfo* cert_request_info, + SSLClientAuthHandler* delegate) + : delegate_(delegate), + cert_request_info_(cert_request_info) { + dialog_ = gtk_dialog_new_with_buttons( + l10n_util::GetStringFUTF8( + IDS_CERT_SELECTOR_DIALOG_TITLE, + UTF8ToUTF16(cert_request_info->host_and_port)).c_str(), + parent, + // Non-modal. + GTK_DIALOG_NO_SEPARATOR, + l10n_util::GetStringUTF8(IDS_PAGEINFO_CERT_INFO_BUTTON).c_str(), + RESPONSE_SHOW_CERT_INFO, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + NULL); + gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), + gtk_util::kContentAreaSpacing); + gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_OK); + + GtkWidget* site_vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_)->vbox), 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.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, gtk_util::kControlSpacing); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_)->vbox), 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(OnComboBoxChanged), + 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); + + PopulateCerts(); + + g_signal_connect(dialog_, "response", G_CALLBACK(OnResponse), this); + g_signal_connect(dialog_, "destroy", G_CALLBACK(OnDestroy), this); +} + +void SSLClientCertificateSelector::Show() { + gtk_widget_show_all(dialog_); +} + +void SSLClientCertificateSelector::PopulateCerts() { + CERTCertList* cert_list = CERT_NewCertList(); + for (size_t i = 0; i < cert_request_info_->client_certs.size(); ++i) { + CERT_AddCertToListTail( + cert_list, + CERT_DupCertificate( + cert_request_info_->client_certs[i]->os_cert_handle())); + } + // Would like to use CERT_GetCertNicknameWithValidity on each cert + // individually instead of having to build a CERTCertList for this, but that + // function is not exported. + CERTCertNicknames* nicknames = CERT_NicknameStringsFromCertList( + cert_list, + const_cast<char*>(l10n_util::GetStringUTF8( + IDS_CERT_SELECTOR_CERT_EXPIRED).c_str()), + const_cast<char*>(l10n_util::GetStringUTF8( + IDS_CERT_SELECTOR_CERT_NOT_YET_VALID).c_str())); + DCHECK_EQ(nicknames->numnicknames, + static_cast<int>(cert_request_info_->client_certs.size())); + + for (size_t i = 0; i < cert_request_info_->client_certs.size(); ++i) { + CERTCertificate* 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->nicknames[i]).c_str()); + } + + CERT_FreeNicknames(nicknames); + CERT_DestroyCertList(cert_list); + + // Auto-select the first cert. + gtk_combo_box_set_active(GTK_COMBO_BOX(cert_combo_box_), 0); +} + +// static +std::string SSLClientCertificateSelector::FormatComboBoxText( + CERTCertificate* cert, const char* nickname) { + std::string rv(nickname); + char* serial_hex = CERT_Hexify(&cert->serialNumber, TRUE); + rv += " ["; + rv += serial_hex; + rv += ']'; + PORT_Free(serial_hex); + return rv; +} + +// static +std::string SSLClientCertificateSelector::FormatDetailsText( + CERTCertificate* cert) { + std::string rv; + + rv += l10n_util::GetStringFUTF8(IDS_CERT_SUBJECTNAME_FORMAT, + UTF8ToUTF16(cert->subjectName)); + + char* serial_hex = CERT_Hexify(&cert->serialNumber, TRUE); + rv += "\n "; + rv += l10n_util::GetStringFUTF8(IDS_CERT_SERIAL_NUMBER_FORMAT, + UTF8ToUTF16(serial_hex)); + PORT_Free(serial_hex); + + PRTime issued, expires; + if (CERT_GetCertTimes(cert, &issued, &expires) == SECSuccess) { + string16 issued_str = WideToUTF16( + base::TimeFormatShortDateAndTime(base::PRTimeToBaseTime(issued))); + string16 expires_str = WideToUTF16( + base::TimeFormatShortDateAndTime(base::PRTimeToBaseTime(expires))); + rv += "\n "; + rv += l10n_util::GetStringFUTF8(IDS_CERT_VALIDITY_RANGE_FORMAT, + issued_str, expires_str); + } + + std::vector<std::string> usages; + psm::GetCertUsageStrings(cert, &usages); + if (usages.size()) { + rv += "\n "; + rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT, + UTF8ToUTF16(JoinString(usages, ','))); + } + + SECItem key_usage; + key_usage.data = NULL; + if (CERT_FindKeyUsageExtension(cert, &key_usage) == SECSuccess) { + std::string key_usage_str = psm::ProcessKeyUsageBitString(&key_usage, ','); + PORT_Free(key_usage.data); + if (!key_usage_str.empty()) { + rv += "\n "; + rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_KEY_USAGE_FORMAT, + UTF8ToUTF16(key_usage_str)); + } + } + + std::vector<std::string> email_addresses; + for (const char* addr = CERT_GetFirstEmailAddress(cert); + addr; addr = CERT_GetNextEmailAddress(cert, addr)) { + // The first email addr (from Subject) may be duplicated in Subject + // Alternative Name, so check subsequent addresses are not equal to the + // first one before adding to the list. + if (!email_addresses.size() || email_addresses[0] != addr) + email_addresses.push_back(addr); + } + 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(cert->issuerName)); + + string16 token(UTF8ToUTF16(psm::GetCertTokenName(cert))); + if (!token.empty()) { + rv += '\n'; + rv += l10n_util::GetStringFUTF8(IDS_CERT_TOKEN_FORMAT, token); + } + + return rv; +} + +// static +void SSLClientCertificateSelector::OnComboBoxChanged( + GtkComboBox* combo_box, SSLClientCertificateSelector* cert_selector) { + int selected = gtk_combo_box_get_active( + GTK_COMBO_BOX(cert_selector->cert_combo_box_)); + if (selected < 0) + return; + gtk_text_buffer_set_text(cert_selector->cert_details_buffer_, + cert_selector->details_strings_[selected].c_str(), + cert_selector->details_strings_[selected].size()); +} + +// static +void SSLClientCertificateSelector::OnResponse( + GtkDialog* dialog, gint response_id, + SSLClientCertificateSelector* cert_selector) { + net::X509Certificate* cert = NULL; + if (response_id == GTK_RESPONSE_OK || + response_id == RESPONSE_SHOW_CERT_INFO) { + int selected = gtk_combo_box_get_active( + GTK_COMBO_BOX(cert_selector->cert_combo_box_)); + if (selected >= 0 && + selected < static_cast<int>( + cert_selector->cert_request_info_->client_certs.size())) + cert = cert_selector->cert_request_info_->client_certs[selected]; + } + if (response_id == RESPONSE_SHOW_CERT_INFO) { + if (cert) + ShowCertificateViewer(GTK_WINDOW(cert_selector->dialog_), + cert->os_cert_handle()); + return; + } + cert_selector->delegate_->CertificateSelected(cert); + gtk_widget_destroy(GTK_WIDGET(dialog)); +} + +// static +void SSLClientCertificateSelector::OnDestroy( + GtkDialog* dialog, + SSLClientCertificateSelector* cert_selector) { + delete cert_selector; +} + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// SSLClientAuthHandler platform specific implementation: + void SSLClientAuthHandler::DoSelectCertificate() { - NOTIMPLEMENTED(); - CertificateSelected(NULL); + // TODO(mattm): Pipe parent gfx::NativeWindow param into here somehow. + (new SSLClientCertificateSelector(NULL, cert_request_info_, this))->Show(); } |