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:
GetWidgetRoot()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
SSLClientCertificateSelector(TabContents * parent,net::SSLCertRequestInfo * cert_request_info,SSLClientAuthHandler * delegate)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
~SSLClientCertificateSelector()192 SSLClientCertificateSelector::~SSLClientCertificateSelector() {
193 root_widget_.Destroy();
194 }
195
Show()196 void SSLClientCertificateSelector::Show() {
197 DCHECK(!window_);
198 window_ = parent_->CreateConstrainedDialog(this);
199 }
200
OnCertSelectedByNotification()201 void SSLClientCertificateSelector::OnCertSelectedByNotification() {
202 delegate_ = NULL;
203 DCHECK(window_);
204 window_->CloseConstrainedWindow();
205 }
206
GetFocusWidget()207 GtkWidget* SSLClientCertificateSelector::GetFocusWidget() {
208 return select_button_;
209 }
210
DeleteDelegate()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
PopulateCerts()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
GetSelectedCert()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
FormatComboBoxText(net::X509Certificate::OSCertHandle cert,const std::string & nickname)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
FormatDetailsText(net::X509Certificate::OSCertHandle cert)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
Unlocked()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
OnComboBoxChanged(GtkWidget * combo_box)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
OnViewClicked(GtkWidget * button)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
OnCancelClicked(GtkWidget * button)354 void SSLClientCertificateSelector::OnCancelClicked(GtkWidget* button) {
355 StopObserving();
356 delegate_->CertificateSelected(NULL);
357 delegate_ = NULL;
358 DCHECK(window_);
359 window_->CloseConstrainedWindow();
360 }
361
OnOkClicked(GtkWidget * button)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
OnPromptShown(GtkWidget * widget,GtkWidget * previous_toplevel)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
ShowSSLClientCertificateSelector(TabContents * parent,net::SSLCertRequestInfo * cert_request_info,SSLClientAuthHandler * delegate)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