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