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 |