OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ssl/ssl_client_auth_handler.h" | 5 #include "chrome/browser/ssl/ssl_client_auth_handler.h" |
| 6 |
| 7 #include <cert.h> |
| 8 #include <gtk/gtk.h> |
| 9 |
| 10 #include <string> |
| 11 #include <vector> |
| 12 |
| 13 #include "app/gfx/native_widget_types.h" |
| 14 #include "app/l10n_util.h" |
| 15 #include "base/i18n/time_formatting.h" |
6 #include "base/logging.h" | 16 #include "base/logging.h" |
| 17 #include "base/nss_util.h" |
| 18 #include "chrome/browser/gtk/certificate_viewer.h" |
| 19 #include "chrome/browser/gtk/gtk_util.h" |
| 20 #include "chrome/third_party/mozilla_security_manager/nsNSSCertHelper.h" |
| 21 #include "chrome/third_party/mozilla_security_manager/nsNSSCertificate.h" |
| 22 #include "chrome/third_party/mozilla_security_manager/nsUsageArrayHelper.h" |
| 23 #include "grit/generated_resources.h" |
7 #include "net/base/x509_certificate.h" | 24 #include "net/base/x509_certificate.h" |
8 | 25 |
| 26 // PSM = Mozilla's Personal Security Manager. |
| 27 namespace psm = mozilla_security_manager; |
| 28 |
| 29 namespace { |
| 30 |
| 31 enum { |
| 32 RESPONSE_SHOW_CERT_INFO = 1, |
| 33 }; |
| 34 |
| 35 |
| 36 /////////////////////////////////////////////////////////////////////////////// |
| 37 // SSLClientCertificateSelector |
| 38 |
| 39 class SSLClientCertificateSelector { |
| 40 public: |
| 41 SSLClientCertificateSelector(gfx::NativeWindow parent, |
| 42 net::SSLCertRequestInfo* cert_request_info, |
| 43 SSLClientAuthHandler* delegate); |
| 44 |
| 45 void Show(); |
| 46 |
| 47 private: |
| 48 void PopulateCerts(); |
| 49 |
| 50 static std::string FormatComboBoxText(CERTCertificate* cert, |
| 51 const char* nickname); |
| 52 static std::string FormatDetailsText(CERTCertificate* cert); |
| 53 |
| 54 static void OnComboBoxChanged(GtkComboBox* combo_box, |
| 55 SSLClientCertificateSelector* cert_selector); |
| 56 static void OnResponse(GtkDialog* dialog, gint response_id, |
| 57 SSLClientCertificateSelector* cert_selector); |
| 58 static void OnDestroy(GtkDialog* dialog, |
| 59 SSLClientCertificateSelector* cert_selector); |
| 60 |
| 61 SSLClientAuthHandler* delegate_; |
| 62 scoped_refptr<net::SSLCertRequestInfo> cert_request_info_; |
| 63 |
| 64 std::vector<std::string> details_strings_; |
| 65 |
| 66 GtkWidget* dialog_; |
| 67 GtkWidget* cert_combo_box_; |
| 68 GtkTextBuffer* cert_details_buffer_; |
| 69 }; |
| 70 |
| 71 SSLClientCertificateSelector::SSLClientCertificateSelector( |
| 72 gfx::NativeWindow parent, |
| 73 net::SSLCertRequestInfo* cert_request_info, |
| 74 SSLClientAuthHandler* delegate) |
| 75 : delegate_(delegate), |
| 76 cert_request_info_(cert_request_info) { |
| 77 dialog_ = gtk_dialog_new_with_buttons( |
| 78 l10n_util::GetStringFUTF8( |
| 79 IDS_CERT_SELECTOR_DIALOG_TITLE, |
| 80 UTF8ToUTF16(cert_request_info->host_and_port)).c_str(), |
| 81 parent, |
| 82 // Non-modal. |
| 83 GTK_DIALOG_NO_SEPARATOR, |
| 84 l10n_util::GetStringUTF8(IDS_PAGEINFO_CERT_INFO_BUTTON).c_str(), |
| 85 RESPONSE_SHOW_CERT_INFO, |
| 86 GTK_STOCK_CANCEL, |
| 87 GTK_RESPONSE_CANCEL, |
| 88 GTK_STOCK_OK, |
| 89 GTK_RESPONSE_OK, |
| 90 NULL); |
| 91 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), |
| 92 gtk_util::kContentAreaSpacing); |
| 93 gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_OK); |
| 94 |
| 95 GtkWidget* site_vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); |
| 96 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_)->vbox), site_vbox, |
| 97 FALSE, FALSE, 0); |
| 98 |
| 99 GtkWidget* site_description_label = gtk_util::CreateBoldLabel( |
| 100 l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_SITE_DESCRIPTION_LABEL)); |
| 101 gtk_box_pack_start(GTK_BOX(site_vbox), site_description_label, |
| 102 FALSE, FALSE, 0); |
| 103 |
| 104 GtkWidget* site_label = gtk_label_new( |
| 105 cert_request_info->host_and_port.c_str()); |
| 106 gtk_util::LeftAlignMisc(site_label); |
| 107 gtk_box_pack_start(GTK_BOX(site_vbox), site_label, FALSE, FALSE, 0); |
| 108 |
| 109 GtkWidget* selector_vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); |
| 110 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_)->vbox), selector_vbox, |
| 111 TRUE, TRUE, 0); |
| 112 |
| 113 GtkWidget* choose_description_label = gtk_util::CreateBoldLabel( |
| 114 l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CHOOSE_DESCRIPTION_LABEL)); |
| 115 gtk_box_pack_start(GTK_BOX(selector_vbox), choose_description_label, |
| 116 FALSE, FALSE, 0); |
| 117 |
| 118 |
| 119 cert_combo_box_ = gtk_combo_box_new_text(); |
| 120 g_signal_connect(cert_combo_box_, "changed", G_CALLBACK(OnComboBoxChanged), |
| 121 this); |
| 122 gtk_box_pack_start(GTK_BOX(selector_vbox), cert_combo_box_, |
| 123 FALSE, FALSE, 0); |
| 124 |
| 125 GtkWidget* details_label = gtk_label_new(l10n_util::GetStringUTF8( |
| 126 IDS_CERT_SELECTOR_DETAILS_DESCRIPTION_LABEL).c_str()); |
| 127 gtk_util::LeftAlignMisc(details_label); |
| 128 gtk_box_pack_start(GTK_BOX(selector_vbox), details_label, FALSE, FALSE, 0); |
| 129 |
| 130 // TODO(mattm): fix text view coloring (should have grey background). |
| 131 GtkWidget* cert_details_view = gtk_text_view_new(); |
| 132 gtk_text_view_set_editable(GTK_TEXT_VIEW(cert_details_view), FALSE); |
| 133 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(cert_details_view), GTK_WRAP_WORD); |
| 134 cert_details_buffer_ = gtk_text_view_get_buffer( |
| 135 GTK_TEXT_VIEW(cert_details_view)); |
| 136 // We put the details in a frame instead of a scrolled window so that the |
| 137 // entirety will be visible without requiring scrolling or expanding the |
| 138 // dialog. This does however mean the dialog will grow itself if you switch |
| 139 // to different cert that has longer details text. |
| 140 GtkWidget* details_frame = gtk_frame_new(NULL); |
| 141 gtk_frame_set_shadow_type(GTK_FRAME(details_frame), GTK_SHADOW_ETCHED_IN); |
| 142 gtk_container_add(GTK_CONTAINER(details_frame), cert_details_view); |
| 143 gtk_box_pack_start(GTK_BOX(selector_vbox), details_frame, TRUE, TRUE, 0); |
| 144 |
| 145 PopulateCerts(); |
| 146 |
| 147 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponse), this); |
| 148 g_signal_connect(dialog_, "destroy", G_CALLBACK(OnDestroy), this); |
| 149 } |
| 150 |
| 151 void SSLClientCertificateSelector::Show() { |
| 152 gtk_widget_show_all(dialog_); |
| 153 } |
| 154 |
| 155 void SSLClientCertificateSelector::PopulateCerts() { |
| 156 CERTCertList* cert_list = CERT_NewCertList(); |
| 157 for (size_t i = 0; i < cert_request_info_->client_certs.size(); ++i) { |
| 158 CERT_AddCertToListTail( |
| 159 cert_list, |
| 160 CERT_DupCertificate( |
| 161 cert_request_info_->client_certs[i]->os_cert_handle())); |
| 162 } |
| 163 // Would like to use CERT_GetCertNicknameWithValidity on each cert |
| 164 // individually instead of having to build a CERTCertList for this, but that |
| 165 // function is not exported. |
| 166 CERTCertNicknames* nicknames = CERT_NicknameStringsFromCertList( |
| 167 cert_list, |
| 168 const_cast<char*>(l10n_util::GetStringUTF8( |
| 169 IDS_CERT_SELECTOR_CERT_EXPIRED).c_str()), |
| 170 const_cast<char*>(l10n_util::GetStringUTF8( |
| 171 IDS_CERT_SELECTOR_CERT_NOT_YET_VALID).c_str())); |
| 172 DCHECK_EQ(nicknames->numnicknames, |
| 173 static_cast<int>(cert_request_info_->client_certs.size())); |
| 174 |
| 175 for (size_t i = 0; i < cert_request_info_->client_certs.size(); ++i) { |
| 176 CERTCertificate* cert = |
| 177 cert_request_info_->client_certs[i]->os_cert_handle(); |
| 178 |
| 179 details_strings_.push_back(FormatDetailsText(cert)); |
| 180 |
| 181 gtk_combo_box_append_text( |
| 182 GTK_COMBO_BOX(cert_combo_box_), |
| 183 FormatComboBoxText(cert, nicknames->nicknames[i]).c_str()); |
| 184 } |
| 185 |
| 186 CERT_FreeNicknames(nicknames); |
| 187 CERT_DestroyCertList(cert_list); |
| 188 |
| 189 // Auto-select the first cert. |
| 190 gtk_combo_box_set_active(GTK_COMBO_BOX(cert_combo_box_), 0); |
| 191 } |
| 192 |
| 193 // static |
| 194 std::string SSLClientCertificateSelector::FormatComboBoxText( |
| 195 CERTCertificate* cert, const char* nickname) { |
| 196 std::string rv(nickname); |
| 197 char* serial_hex = CERT_Hexify(&cert->serialNumber, TRUE); |
| 198 rv += " ["; |
| 199 rv += serial_hex; |
| 200 rv += ']'; |
| 201 PORT_Free(serial_hex); |
| 202 return rv; |
| 203 } |
| 204 |
| 205 // static |
| 206 std::string SSLClientCertificateSelector::FormatDetailsText( |
| 207 CERTCertificate* cert) { |
| 208 std::string rv; |
| 209 |
| 210 rv += l10n_util::GetStringFUTF8(IDS_CERT_SUBJECTNAME_FORMAT, |
| 211 UTF8ToUTF16(cert->subjectName)); |
| 212 |
| 213 char* serial_hex = CERT_Hexify(&cert->serialNumber, TRUE); |
| 214 rv += "\n "; |
| 215 rv += l10n_util::GetStringFUTF8(IDS_CERT_SERIAL_NUMBER_FORMAT, |
| 216 UTF8ToUTF16(serial_hex)); |
| 217 PORT_Free(serial_hex); |
| 218 |
| 219 PRTime issued, expires; |
| 220 if (CERT_GetCertTimes(cert, &issued, &expires) == SECSuccess) { |
| 221 string16 issued_str = WideToUTF16( |
| 222 base::TimeFormatShortDateAndTime(base::PRTimeToBaseTime(issued))); |
| 223 string16 expires_str = WideToUTF16( |
| 224 base::TimeFormatShortDateAndTime(base::PRTimeToBaseTime(expires))); |
| 225 rv += "\n "; |
| 226 rv += l10n_util::GetStringFUTF8(IDS_CERT_VALIDITY_RANGE_FORMAT, |
| 227 issued_str, expires_str); |
| 228 } |
| 229 |
| 230 std::vector<std::string> usages; |
| 231 psm::GetCertUsageStrings(cert, &usages); |
| 232 if (usages.size()) { |
| 233 rv += "\n "; |
| 234 rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT, |
| 235 UTF8ToUTF16(JoinString(usages, ','))); |
| 236 } |
| 237 |
| 238 SECItem key_usage; |
| 239 key_usage.data = NULL; |
| 240 if (CERT_FindKeyUsageExtension(cert, &key_usage) == SECSuccess) { |
| 241 std::string key_usage_str = psm::ProcessKeyUsageBitString(&key_usage, ','); |
| 242 PORT_Free(key_usage.data); |
| 243 if (!key_usage_str.empty()) { |
| 244 rv += "\n "; |
| 245 rv += l10n_util::GetStringFUTF8(IDS_CERT_X509_KEY_USAGE_FORMAT, |
| 246 UTF8ToUTF16(key_usage_str)); |
| 247 } |
| 248 } |
| 249 |
| 250 std::vector<std::string> email_addresses; |
| 251 for (const char* addr = CERT_GetFirstEmailAddress(cert); |
| 252 addr; addr = CERT_GetNextEmailAddress(cert, addr)) { |
| 253 // The first email addr (from Subject) may be duplicated in Subject |
| 254 // Alternative Name, so check subsequent addresses are not equal to the |
| 255 // first one before adding to the list. |
| 256 if (!email_addresses.size() || email_addresses[0] != addr) |
| 257 email_addresses.push_back(addr); |
| 258 } |
| 259 if (email_addresses.size()) { |
| 260 rv += "\n "; |
| 261 rv += l10n_util::GetStringFUTF8( |
| 262 IDS_CERT_EMAIL_ADDRESSES_FORMAT, |
| 263 UTF8ToUTF16(JoinString(email_addresses, ','))); |
| 264 } |
| 265 |
| 266 rv += '\n'; |
| 267 rv += l10n_util::GetStringFUTF8(IDS_CERT_ISSUERNAME_FORMAT, |
| 268 UTF8ToUTF16(cert->issuerName)); |
| 269 |
| 270 string16 token(UTF8ToUTF16(psm::GetCertTokenName(cert))); |
| 271 if (!token.empty()) { |
| 272 rv += '\n'; |
| 273 rv += l10n_util::GetStringFUTF8(IDS_CERT_TOKEN_FORMAT, token); |
| 274 } |
| 275 |
| 276 return rv; |
| 277 } |
| 278 |
| 279 // static |
| 280 void SSLClientCertificateSelector::OnComboBoxChanged( |
| 281 GtkComboBox* combo_box, SSLClientCertificateSelector* cert_selector) { |
| 282 int selected = gtk_combo_box_get_active( |
| 283 GTK_COMBO_BOX(cert_selector->cert_combo_box_)); |
| 284 if (selected < 0) |
| 285 return; |
| 286 gtk_text_buffer_set_text(cert_selector->cert_details_buffer_, |
| 287 cert_selector->details_strings_[selected].c_str(), |
| 288 cert_selector->details_strings_[selected].size()); |
| 289 } |
| 290 |
| 291 // static |
| 292 void SSLClientCertificateSelector::OnResponse( |
| 293 GtkDialog* dialog, gint response_id, |
| 294 SSLClientCertificateSelector* cert_selector) { |
| 295 net::X509Certificate* cert = NULL; |
| 296 if (response_id == GTK_RESPONSE_OK || |
| 297 response_id == RESPONSE_SHOW_CERT_INFO) { |
| 298 int selected = gtk_combo_box_get_active( |
| 299 GTK_COMBO_BOX(cert_selector->cert_combo_box_)); |
| 300 if (selected >= 0 && |
| 301 selected < static_cast<int>( |
| 302 cert_selector->cert_request_info_->client_certs.size())) |
| 303 cert = cert_selector->cert_request_info_->client_certs[selected]; |
| 304 } |
| 305 if (response_id == RESPONSE_SHOW_CERT_INFO) { |
| 306 if (cert) |
| 307 ShowCertificateViewer(GTK_WINDOW(cert_selector->dialog_), |
| 308 cert->os_cert_handle()); |
| 309 return; |
| 310 } |
| 311 cert_selector->delegate_->CertificateSelected(cert); |
| 312 gtk_widget_destroy(GTK_WIDGET(dialog)); |
| 313 } |
| 314 |
| 315 // static |
| 316 void SSLClientCertificateSelector::OnDestroy( |
| 317 GtkDialog* dialog, |
| 318 SSLClientCertificateSelector* cert_selector) { |
| 319 delete cert_selector; |
| 320 } |
| 321 |
| 322 } // namespace |
| 323 |
| 324 /////////////////////////////////////////////////////////////////////////////// |
| 325 // SSLClientAuthHandler platform specific implementation: |
| 326 |
9 void SSLClientAuthHandler::DoSelectCertificate() { | 327 void SSLClientAuthHandler::DoSelectCertificate() { |
10 NOTIMPLEMENTED(); | 328 // TODO(mattm): Pipe parent gfx::NativeWindow param into here somehow. |
11 CertificateSelected(NULL); | 329 (new SSLClientCertificateSelector(NULL, cert_request_info_, this))->Show(); |
12 } | 330 } |
OLD | NEW |