Home | History | Annotate | Download | only in gtk
      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:
     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 
     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 
    192 SSLClientCertificateSelector::~SSLClientCertificateSelector() {
    193   root_widget_.Destroy();
    194 }
    195 
    196 void SSLClientCertificateSelector::Show() {
    197   DCHECK(!window_);
    198   window_ = parent_->CreateConstrainedDialog(this);
    199 }
    200 
    201 void SSLClientCertificateSelector::OnCertSelectedByNotification() {
    202   delegate_ = NULL;
    203   DCHECK(window_);
    204   window_->CloseConstrainedWindow();
    205 }
    206 
    207 GtkWidget* SSLClientCertificateSelector::GetFocusWidget() {
    208   return select_button_;
    209 }
    210 
    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 
    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 
    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
    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
    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 
    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 
    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 
    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 
    354 void SSLClientCertificateSelector::OnCancelClicked(GtkWidget* button) {
    355   StopObserving();
    356   delegate_->CertificateSelected(NULL);
    357   delegate_ = NULL;
    358   DCHECK(window_);
    359   window_->CloseConstrainedWindow();
    360 }
    361 
    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 
    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 
    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