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.
5 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
12 #include "base/bind.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/logging.h"
15 #include "base/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.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 "content/public/browser/browser_thread.h"
23 #include "grit/generated_resources.h"
24 #include "net/base/ssl_cert_request_info.h"
25 #include "net/base/x509_certificate.h"
26 #include "ui/base/gtk/gtk_compat.h"
27 #include "ui/base/gtk/gtk_hig_constants.h"
28 #include "ui/base/gtk/gtk_signal.h"
29 #include "ui/base/gtk/owned_widget_gtk.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/gfx/native_widget_types.h"
33 using content::BrowserThread
;
34 using content::WebContents
;
39 RESPONSE_SHOW_CERT_INFO
= 1,
42 ///////////////////////////////////////////////////////////////////////////////
43 // SSLClientCertificateSelector
45 class SSLClientCertificateSelector
: public SSLClientAuthObserver
,
46 public ConstrainedWindowGtkDelegate
{
48 explicit SSLClientCertificateSelector(
50 const net::HttpNetworkSession
* network_session
,
51 net::SSLCertRequestInfo
* cert_request_info
,
52 const base::Callback
<void(net::X509Certificate
*)>& callback
);
53 virtual ~SSLClientCertificateSelector();
57 // SSLClientAuthObserver implementation:
58 virtual void OnCertSelectedByNotification() OVERRIDE
;
60 // ConstrainedWindowGtkDelegate implementation:
61 virtual GtkWidget
* GetWidgetRoot() OVERRIDE
{ return root_widget_
.get(); }
62 virtual GtkWidget
* GetFocusWidget() OVERRIDE
;
63 virtual void DeleteDelegate() OVERRIDE
;
68 net::X509Certificate
* GetSelectedCert();
70 static std::string
FormatComboBoxText(
71 net::X509Certificate::OSCertHandle cert
,
72 const std::string
& nickname
);
73 static std::string
FormatDetailsText(
74 net::X509Certificate::OSCertHandle cert
);
76 // Callback after unlocking certificate slot.
79 CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector
, void, OnComboBoxChanged
);
80 CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector
, void, OnViewClicked
);
81 CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector
, void, OnCancelClicked
);
82 CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector
, void, OnOkClicked
);
83 CHROMEGTK_CALLBACK_1(SSLClientCertificateSelector
, void, OnPromptShown
,
86 std::vector
<std::string
> details_strings_
;
88 GtkWidget
* cert_combo_box_
;
89 GtkTextBuffer
* cert_details_buffer_
;
91 ui::OwnedWidgetGtk root_widget_
;
92 // Hold on to the select button to focus it.
93 GtkWidget
* select_button_
;
95 WebContents
* web_contents_
;
96 ConstrainedWindowGtk
* window_
;
98 DISALLOW_COPY_AND_ASSIGN(SSLClientCertificateSelector
);
101 SSLClientCertificateSelector::SSLClientCertificateSelector(
102 WebContents
* web_contents
,
103 const net::HttpNetworkSession
* network_session
,
104 net::SSLCertRequestInfo
* cert_request_info
,
105 const base::Callback
<void(net::X509Certificate
*)>& callback
)
106 : SSLClientAuthObserver(network_session
, cert_request_info
, callback
),
107 web_contents_(web_contents
),
109 root_widget_
.Own(gtk_vbox_new(FALSE
, ui::kControlSpacing
));
111 GtkWidget
* site_vbox
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
112 gtk_box_pack_start(GTK_BOX(root_widget_
.get()), site_vbox
,
115 GtkWidget
* site_description_label
= gtk_util::CreateBoldLabel(
116 l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_SITE_DESCRIPTION_LABEL
));
117 gtk_box_pack_start(GTK_BOX(site_vbox
), site_description_label
,
120 GtkWidget
* site_label
= gtk_label_new(
121 cert_request_info
->host_and_port
.c_str());
122 gtk_util::LeftAlignMisc(site_label
);
123 gtk_box_pack_start(GTK_BOX(site_vbox
), site_label
, FALSE
, FALSE
, 0);
125 GtkWidget
* selector_vbox
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
126 gtk_box_pack_start(GTK_BOX(root_widget_
.get()), selector_vbox
,
129 GtkWidget
* choose_description_label
= gtk_util::CreateBoldLabel(
130 l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CHOOSE_DESCRIPTION_LABEL
));
131 gtk_box_pack_start(GTK_BOX(selector_vbox
), choose_description_label
,
135 cert_combo_box_
= gtk_combo_box_new_text();
136 g_signal_connect(cert_combo_box_
, "changed",
137 G_CALLBACK(OnComboBoxChangedThunk
), this);
138 gtk_box_pack_start(GTK_BOX(selector_vbox
), cert_combo_box_
,
141 GtkWidget
* details_label
= gtk_label_new(l10n_util::GetStringUTF8(
142 IDS_CERT_SELECTOR_DETAILS_DESCRIPTION_LABEL
).c_str());
143 gtk_util::LeftAlignMisc(details_label
);
144 gtk_box_pack_start(GTK_BOX(selector_vbox
), details_label
, FALSE
, FALSE
, 0);
146 // TODO(mattm): fix text view coloring (should have grey background).
147 GtkWidget
* cert_details_view
= gtk_text_view_new();
148 gtk_text_view_set_editable(GTK_TEXT_VIEW(cert_details_view
), FALSE
);
149 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(cert_details_view
), GTK_WRAP_WORD
);
150 cert_details_buffer_
= gtk_text_view_get_buffer(
151 GTK_TEXT_VIEW(cert_details_view
));
152 // We put the details in a frame instead of a scrolled window so that the
153 // entirety will be visible without requiring scrolling or expanding the
154 // dialog. This does however mean the dialog will grow itself if you switch
155 // to different cert that has longer details text.
156 GtkWidget
* details_frame
= gtk_frame_new(NULL
);
157 gtk_frame_set_shadow_type(GTK_FRAME(details_frame
), GTK_SHADOW_ETCHED_IN
);
158 gtk_container_add(GTK_CONTAINER(details_frame
), cert_details_view
);
159 gtk_box_pack_start(GTK_BOX(selector_vbox
), details_frame
, TRUE
, TRUE
, 0);
161 // And then create a set of buttons like a GtkDialog would.
162 GtkWidget
* button_box
= gtk_hbutton_box_new();
163 gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box
), GTK_BUTTONBOX_END
);
164 gtk_box_set_spacing(GTK_BOX(button_box
), ui::kControlSpacing
);
165 gtk_box_pack_end(GTK_BOX(root_widget_
.get()), button_box
, FALSE
, FALSE
, 0);
167 GtkWidget
* view_button
= gtk_button_new_with_mnemonic(
168 l10n_util::GetStringUTF8(IDS_PAGEINFO_CERT_INFO_BUTTON
).c_str());
169 gtk_box_pack_start(GTK_BOX(button_box
), view_button
, FALSE
, FALSE
, 0);
170 g_signal_connect(view_button
, "clicked",
171 G_CALLBACK(OnViewClickedThunk
), this);
173 GtkWidget
* cancel_button
= gtk_button_new_from_stock(GTK_STOCK_CANCEL
);
174 gtk_box_pack_end(GTK_BOX(button_box
), cancel_button
, FALSE
, FALSE
, 0);
175 g_signal_connect(cancel_button
, "clicked",
176 G_CALLBACK(OnCancelClickedThunk
), this);
178 GtkWidget
* select_button
= gtk_button_new_from_stock(GTK_STOCK_OK
);
179 gtk_box_pack_end(GTK_BOX(button_box
), select_button
, FALSE
, FALSE
, 0);
180 g_signal_connect(select_button
, "clicked",
181 G_CALLBACK(OnOkClickedThunk
), this);
183 // When we are attached to a window, focus the select button.
184 select_button_
= select_button
;
185 g_signal_connect(root_widget_
.get(), "hierarchy-changed",
186 G_CALLBACK(OnPromptShownThunk
), this);
189 gtk_widget_show_all(root_widget_
.get());
194 SSLClientCertificateSelector::~SSLClientCertificateSelector() {
195 root_widget_
.Destroy();
198 void SSLClientCertificateSelector::Show() {
200 window_
= new ConstrainedWindowGtk(web_contents_
, this);
203 void SSLClientCertificateSelector::OnCertSelectedByNotification() {
205 gtk_widget_destroy(window_
->widget());
208 GtkWidget
* SSLClientCertificateSelector::GetFocusWidget() {
209 return select_button_
;
212 void SSLClientCertificateSelector::DeleteDelegate() {
213 // The dialog was closed by escape key.
215 CertificateSelected(NULL
);
219 void SSLClientCertificateSelector::PopulateCerts() {
220 std::vector
<std::string
> nicknames
;
221 x509_certificate_model::GetNicknameStringsFromCertList(
222 cert_request_info()->client_certs
,
223 l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CERT_EXPIRED
),
224 l10n_util::GetStringUTF8(IDS_CERT_SELECTOR_CERT_NOT_YET_VALID
),
227 DCHECK_EQ(nicknames
.size(),
228 cert_request_info()->client_certs
.size());
230 for (size_t i
= 0; i
< cert_request_info()->client_certs
.size(); ++i
) {
231 net::X509Certificate::OSCertHandle cert
=
232 cert_request_info()->client_certs
[i
]->os_cert_handle();
234 details_strings_
.push_back(FormatDetailsText(cert
));
236 gtk_combo_box_append_text(
237 GTK_COMBO_BOX(cert_combo_box_
),
238 FormatComboBoxText(cert
, nicknames
[i
]).c_str());
241 // Auto-select the first cert.
242 gtk_combo_box_set_active(GTK_COMBO_BOX(cert_combo_box_
), 0);
245 net::X509Certificate
* SSLClientCertificateSelector::GetSelectedCert() {
246 int selected
= gtk_combo_box_get_active(GTK_COMBO_BOX(cert_combo_box_
));
248 selected
< static_cast<int>(
249 cert_request_info()->client_certs
.size()))
250 return cert_request_info()->client_certs
[selected
];
255 std::string
SSLClientCertificateSelector::FormatComboBoxText(
256 net::X509Certificate::OSCertHandle cert
, const std::string
& nickname
) {
257 std::string
rv(nickname
);
259 rv
+= x509_certificate_model::GetSerialNumberHexified(cert
, "");
265 std::string
SSLClientCertificateSelector::FormatDetailsText(
266 net::X509Certificate::OSCertHandle cert
) {
269 rv
+= l10n_util::GetStringFUTF8(
270 IDS_CERT_SUBJECTNAME_FORMAT
,
271 UTF8ToUTF16(x509_certificate_model::GetSubjectName(cert
)));
274 rv
+= l10n_util::GetStringFUTF8(
275 IDS_CERT_SERIAL_NUMBER_FORMAT
,
277 x509_certificate_model::GetSerialNumberHexified(cert
, "")));
279 base::Time issued
, expires
;
280 if (x509_certificate_model::GetTimes(cert
, &issued
, &expires
)) {
281 string16 issued_str
= base::TimeFormatShortDateAndTime(issued
);
282 string16 expires_str
= base::TimeFormatShortDateAndTime(expires
);
284 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_VALIDITY_RANGE_FORMAT
,
285 issued_str
, expires_str
);
288 std::vector
<std::string
> usages
;
289 x509_certificate_model::GetUsageStrings(cert
, &usages
);
292 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT
,
293 UTF8ToUTF16(JoinString(usages
, ',')));
296 std::string key_usage_str
= x509_certificate_model::GetKeyUsageString(cert
);
297 if (!key_usage_str
.empty()) {
299 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_X509_KEY_USAGE_FORMAT
,
300 UTF8ToUTF16(key_usage_str
));
303 std::vector
<std::string
> email_addresses
;
304 x509_certificate_model::GetEmailAddresses(cert
, &email_addresses
);
305 if (email_addresses
.size()) {
307 rv
+= l10n_util::GetStringFUTF8(
308 IDS_CERT_EMAIL_ADDRESSES_FORMAT
,
309 UTF8ToUTF16(JoinString(email_addresses
, ',')));
313 rv
+= l10n_util::GetStringFUTF8(
314 IDS_CERT_ISSUERNAME_FORMAT
,
315 UTF8ToUTF16(x509_certificate_model::GetIssuerName(cert
)));
317 string16
token(UTF8ToUTF16(x509_certificate_model::GetTokenName(cert
)));
318 if (!token
.empty()) {
320 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_TOKEN_FORMAT
, token
);
326 void SSLClientCertificateSelector::Unlocked() {
327 // TODO(mattm): refactor so we don't need to call GetSelectedCert again.
328 net::X509Certificate
* cert
= GetSelectedCert();
329 CertificateSelected(cert
);
331 gtk_widget_destroy(window_
->widget());
334 void SSLClientCertificateSelector::OnComboBoxChanged(GtkWidget
* combo_box
) {
335 int selected
= gtk_combo_box_get_active(
336 GTK_COMBO_BOX(cert_combo_box_
));
339 gtk_text_buffer_set_text(cert_details_buffer_
,
340 details_strings_
[selected
].c_str(),
341 details_strings_
[selected
].size());
344 void SSLClientCertificateSelector::OnViewClicked(GtkWidget
* button
) {
345 net::X509Certificate
* cert
= GetSelectedCert();
347 GtkWidget
* toplevel
= gtk_widget_get_toplevel(root_widget_
.get());
348 ShowCertificateViewer(web_contents_
, GTK_WINDOW(toplevel
), cert
);
352 void SSLClientCertificateSelector::OnCancelClicked(GtkWidget
* button
) {
353 CertificateSelected(NULL
);
355 gtk_widget_destroy(window_
->widget());
358 void SSLClientCertificateSelector::OnOkClicked(GtkWidget
* button
) {
359 net::X509Certificate
* cert
= GetSelectedCert();
361 // Remove the observer before we try unlocking, otherwise we might act on a
362 // notification while waiting for the unlock dialog, causing us to delete
363 // ourself before the Unlocked callback gets called.
366 chrome::UnlockCertSlotIfNecessary(
368 chrome::kCryptoModulePasswordClientAuth
,
369 cert_request_info()->host_and_port
,
370 base::Bind(&SSLClientCertificateSelector::Unlocked
,
371 base::Unretained(this)));
374 void SSLClientCertificateSelector::OnPromptShown(GtkWidget
* widget
,
375 GtkWidget
* previous_toplevel
) {
376 if (!root_widget_
.get() ||
377 !gtk_widget_is_toplevel(gtk_widget_get_toplevel(root_widget_
.get())))
379 gtk_widget_set_can_default(select_button_
, TRUE
);
380 gtk_widget_grab_default(select_button_
);
387 void ShowSSLClientCertificateSelector(
388 content::WebContents
* contents
,
389 const net::HttpNetworkSession
* network_session
,
390 net::SSLCertRequestInfo
* cert_request_info
,
391 const base::Callback
<void(net::X509Certificate
*)>& callback
) {
392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
393 (new SSLClientCertificateSelector(
394 contents
, network_session
, cert_request_info
, callback
))->Show();
397 } // namespace chrome