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