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