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/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"
34 using content::BrowserThread
;
35 using content::WebContents
;
36 using web_modal::WebContentsModalDialogManager
;
41 RESPONSE_SHOW_CERT_INFO
= 1,
44 ///////////////////////////////////////////////////////////////////////////////
45 // SSLClientCertificateSelector
47 class SSLClientCertificateSelector
: public SSLClientAuthObserver
{
49 explicit SSLClientCertificateSelector(
51 const net::HttpNetworkSession
* network_session
,
52 net::SSLCertRequestInfo
* cert_request_info
,
53 const base::Callback
<void(net::X509Certificate
*)>& callback
);
54 virtual ~SSLClientCertificateSelector();
58 // SSLClientAuthObserver implementation:
59 virtual void OnCertSelectedByNotification() OVERRIDE
;
64 net::X509Certificate
* GetSelectedCert();
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
);
72 // Callback after unlocking certificate slot.
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
,
81 CHROMEGTK_CALLBACK_0(SSLClientCertificateSelector
, void, OnDestroy
);
83 std::vector
<std::string
> details_strings_
;
85 GtkWidget
* cert_combo_box_
;
86 GtkTextBuffer
* cert_details_buffer_
;
88 ui::ScopedGObject
<GtkWidget
>::Type root_widget_
;
89 // Hold on to the select button to focus it.
90 GtkWidget
* select_button_
;
92 WebContents
* web_contents_
;
95 DISALLOW_COPY_AND_ASSIGN(SSLClientCertificateSelector
);
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
),
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(),
110 G_CALLBACK(OnDestroyThunk
),
113 GtkWidget
* site_vbox
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
114 gtk_box_pack_start(GTK_BOX(root_widget_
.get()), site_vbox
,
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
,
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);
127 GtkWidget
* selector_vbox
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
128 gtk_box_pack_start(GTK_BOX(root_widget_
.get()), selector_vbox
,
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
,
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_
,
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);
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);
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);
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);
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);
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);
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);
191 gtk_widget_show_all(root_widget_
.get());
196 SSLClientCertificateSelector::~SSLClientCertificateSelector() {
199 void SSLClientCertificateSelector::Show() {
201 window_
= CreateWebContentsModalDialogGtk(root_widget_
.get(), select_button_
);
203 WebContentsModalDialogManager
* web_contents_modal_dialog_manager
=
204 WebContentsModalDialogManager::FromWebContents(web_contents_
);
205 web_contents_modal_dialog_manager
->ShowDialog(window_
);
208 void SSLClientCertificateSelector::OnCertSelectedByNotification() {
210 gtk_widget_destroy(window_
);
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
),
221 DCHECK_EQ(nicknames
.size(),
222 cert_request_info()->client_certs
.size());
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();
228 details_strings_
.push_back(FormatDetailsText(cert
));
230 gtk_combo_box_append_text(
231 GTK_COMBO_BOX(cert_combo_box_
),
232 FormatComboBoxText(cert
, nicknames
[i
]).c_str());
235 // Auto-select the first cert.
236 gtk_combo_box_set_active(GTK_COMBO_BOX(cert_combo_box_
), 0);
239 net::X509Certificate
* SSLClientCertificateSelector::GetSelectedCert() {
240 int selected
= gtk_combo_box_get_active(GTK_COMBO_BOX(cert_combo_box_
));
242 selected
< static_cast<int>(
243 cert_request_info()->client_certs
.size()))
244 return cert_request_info()->client_certs
[selected
].get();
249 std::string
SSLClientCertificateSelector::FormatComboBoxText(
250 net::X509Certificate::OSCertHandle cert
, const std::string
& nickname
) {
251 std::string
rv(nickname
);
253 rv
+= x509_certificate_model::GetSerialNumberHexified(cert
, std::string());
259 std::string
SSLClientCertificateSelector::FormatDetailsText(
260 net::X509Certificate::OSCertHandle cert
) {
263 rv
+= l10n_util::GetStringFUTF8(
264 IDS_CERT_SUBJECTNAME_FORMAT
,
265 base::UTF8ToUTF16(x509_certificate_model::GetSubjectName(cert
)));
268 rv
+= l10n_util::GetStringFUTF8(
269 IDS_CERT_SERIAL_NUMBER_FORMAT
,
270 base::UTF8ToUTF16(x509_certificate_model::GetSerialNumberHexified(
271 cert
, std::string())));
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
);
278 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_VALIDITY_RANGE_FORMAT
,
279 issued_str
, expires_str
);
282 std::vector
<std::string
> usages
;
283 x509_certificate_model::GetUsageStrings(cert
, &usages
);
286 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_X509_EXTENDED_KEY_USAGE_FORMAT
,
287 base::UTF8ToUTF16(JoinString(usages
, ',')));
290 std::string key_usage_str
= x509_certificate_model::GetKeyUsageString(cert
);
291 if (!key_usage_str
.empty()) {
293 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_X509_KEY_USAGE_FORMAT
,
294 base::UTF8ToUTF16(key_usage_str
));
297 std::vector
<std::string
> email_addresses
;
298 x509_certificate_model::GetEmailAddresses(cert
, &email_addresses
);
299 if (email_addresses
.size()) {
301 rv
+= l10n_util::GetStringFUTF8(
302 IDS_CERT_EMAIL_ADDRESSES_FORMAT
,
303 base::UTF8ToUTF16(JoinString(email_addresses
, ',')));
307 rv
+= l10n_util::GetStringFUTF8(
308 IDS_CERT_ISSUERNAME_FORMAT
,
309 base::UTF8ToUTF16(x509_certificate_model::GetIssuerName(cert
)));
311 base::string16
token(
312 base::UTF8ToUTF16(x509_certificate_model::GetTokenName(cert
)));
313 if (!token
.empty()) {
315 rv
+= l10n_util::GetStringFUTF8(IDS_CERT_TOKEN_FORMAT
, token
);
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
);
326 gtk_widget_destroy(window_
);
329 void SSLClientCertificateSelector::OnComboBoxChanged(GtkWidget
* combo_box
) {
330 int selected
= gtk_combo_box_get_active(
331 GTK_COMBO_BOX(cert_combo_box_
));
334 gtk_text_buffer_set_text(cert_details_buffer_
,
335 details_strings_
[selected
].c_str(),
336 details_strings_
[selected
].size());
339 void SSLClientCertificateSelector::OnViewClicked(GtkWidget
* button
) {
340 net::X509Certificate
* cert
= GetSelectedCert();
342 GtkWidget
* toplevel
= gtk_widget_get_toplevel(root_widget_
.get());
343 ShowCertificateViewer(web_contents_
, GTK_WINDOW(toplevel
), cert
);
347 void SSLClientCertificateSelector::OnCancelClicked(GtkWidget
* button
) {
348 CertificateSelected(NULL
);
350 gtk_widget_destroy(window_
);
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.
360 GtkWidget
* toplevel
= gtk_widget_get_toplevel(root_widget_
.get());
361 net::X509Certificate
* cert
= GetSelectedCert();
363 chrome::UnlockCertSlotIfNecessary(
365 chrome::kCryptoModulePasswordClientAuth
,
366 cert_request_info()->host_and_port
,
367 GTK_WINDOW(toplevel
),
368 base::Bind(&SSLClientCertificateSelector::Unlocked
,
369 base::Unretained(this)));
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())))
380 gtk_widget_set_can_default(select_button_
, TRUE
);
381 gtk_widget_grab_default(select_button_
);
384 void SSLClientCertificateSelector::OnDestroy(GtkWidget
* widget
) {
385 // The dialog was closed by escape key.
387 CertificateSelected(NULL
);
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(BrowserThread::CurrentlyOn(BrowserThread::UI
));
401 (new SSLClientCertificateSelector(
402 contents
, network_session
, cert_request_info
, callback
))->Show();
405 } // namespace chrome