// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/gtk/certificate_manager.h" #include #include #include #include #include #include "app/gtk_signal.h" #include "app/l10n_util.h" #include "app/l10n_util_collator.h" #include "base/i18n/time_formatting.h" #include "base/nss_util.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/gtk/certificate_viewer.h" #include "chrome/browser/gtk/gtk_util.h" #include "chrome/third_party/mozilla_security_manager/nsNSSCertHelper.h" #include "chrome/third_party/mozilla_security_manager/nsNSSCertificate.h" #include "grit/generated_resources.h" // PSM = Mozilla's Personal Security Manager. namespace psm = mozilla_security_manager; namespace { // Convert a char* return value from NSS into a std::string and free the NSS // memory. If the arg is NULL, an empty string will be returned instead. std::string Stringize(char* nss_text) { std::string s; if (nss_text) { s = nss_text; PORT_Free(nss_text); } return s; } //////////////////////////////////////////////////////////////////////////////// // CertificatePage class definition. class CertificatePage { public: explicit CertificatePage(psm::CertType type); void PopulateTree(CERTCertList* cert_list); // Get the top-level widget of this page. GtkWidget* widget() { return vbox_; } private: // Columns of the tree store. enum { CERT_NAME, CERT_SECURITY_DEVICE, CERT_SERIAL_NUMBER, CERT_EXPIRES_ON, CERT_EXPIRES_ON_INT, CERT_ADDRESS, CERT_POINTER, CERT_STORE_NUM_COLUMNS }; gint LocaleSortFunc(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b, int col); // Gtk event callbacks. CHROMEG_CALLBACK_2(CertificatePage, gint, SortNameFunc, GtkTreeModel*, GtkTreeIter*, GtkTreeIter*); CHROMEG_CALLBACK_2(CertificatePage, gint, SortDeviceFunc, GtkTreeModel*, GtkTreeIter*, GtkTreeIter*); CHROMEG_CALLBACK_0(CertificatePage, void, OnSelectionChanged, GtkTreeSelection*); CHROMEGTK_CALLBACK_0(CertificatePage, void, OnViewClicked); psm::CertType type_; // The top-level widget of this page. GtkWidget* vbox_; GtkWidget* tree_; GtkTreeStore* store_; GtkTreeSelection* selection_; scoped_ptr collator_; GtkWidget* view_button_; }; //////////////////////////////////////////////////////////////////////////////// // CertificatePage implementation. CertificatePage::CertificatePage(psm::CertType type) : type_(type) { vbox_ = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); gtk_container_set_border_width(GTK_CONTAINER(vbox_), gtk_util::kContentAreaBorder); static const int kDescriptionIds[] = { IDS_CERT_MANAGER_USER_TREE_DESCRIPTION, IDS_CERT_MANAGER_OTHER_PEOPLE_TREE_DESCRIPTION, IDS_CERT_MANAGER_SERVER_TREE_DESCRIPTION, IDS_CERT_MANAGER_AUTHORITIES_TREE_DESCRIPTION, IDS_CERT_MANAGER_UNKNOWN_TREE_DESCRIPTION, }; DCHECK_EQ(arraysize(kDescriptionIds), static_cast(psm::NUM_CERT_TYPES)); GtkWidget* description_label = gtk_label_new(l10n_util::GetStringUTF8( kDescriptionIds[type]).c_str()); gtk_util::LeftAlignMisc(description_label); gtk_box_pack_start(GTK_BOX(vbox_), description_label, FALSE, FALSE, 0); store_ = gtk_tree_store_new(CERT_STORE_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT64, G_TYPE_STRING, G_TYPE_POINTER); tree_ = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store_)); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_), TRUE); selection_ = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_)); gtk_tree_selection_set_mode(selection_, GTK_SELECTION_SINGLE); g_signal_connect(selection_, "changed", G_CALLBACK(OnSelectionChangedThunk), this); GtkTreeViewColumn* name_col = gtk_tree_view_column_new_with_attributes( l10n_util::GetStringUTF8(IDS_CERT_MANAGER_NAME_COLUMN_LABEL).c_str(), gtk_cell_renderer_text_new(), "text", CERT_NAME, NULL); gtk_tree_view_column_set_sort_column_id(name_col, CERT_NAME); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), name_col); if (type == psm::USER_CERT || type == psm::CA_CERT || type == psm::UNKNOWN_CERT) { GtkTreeViewColumn* device_col = gtk_tree_view_column_new_with_attributes( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_DEVICE_COLUMN_LABEL).c_str(), gtk_cell_renderer_text_new(), "text", CERT_SECURITY_DEVICE, NULL); gtk_tree_view_column_set_sort_column_id(device_col, CERT_SECURITY_DEVICE); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), device_col); } if (type == psm::USER_CERT) { GtkTreeViewColumn* serial_col = gtk_tree_view_column_new_with_attributes( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_SERIAL_NUMBER_COLUMN_LABEL).c_str(), gtk_cell_renderer_text_new(), "text", CERT_SERIAL_NUMBER, NULL); gtk_tree_view_column_set_sort_column_id(serial_col, CERT_SERIAL_NUMBER); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), serial_col); } if (type == psm::USER_CERT || type == psm::EMAIL_CERT || type == psm::SERVER_CERT) { GtkTreeViewColumn* expires_col = gtk_tree_view_column_new_with_attributes( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_EXPIRES_COLUMN_LABEL).c_str(), gtk_cell_renderer_text_new(), "text", CERT_EXPIRES_ON, NULL); gtk_tree_view_column_set_sort_column_id(expires_col, CERT_EXPIRES_ON_INT); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), expires_col); } if (type == psm::EMAIL_CERT) { GtkTreeViewColumn* addr_col = gtk_tree_view_column_new_with_attributes( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_EMAIL_ADDRESS_COLUMN_LABEL).c_str(), gtk_cell_renderer_text_new(), "text", CERT_ADDRESS, NULL); gtk_tree_view_column_set_sort_column_id(addr_col, CERT_ADDRESS); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_), addr_col); } UErrorCode error = U_ZERO_ERROR; collator_.reset( icu::Collator::createInstance( icu::Locale(g_browser_process->GetApplicationLocale().c_str()), error)); if (U_FAILURE(error)) collator_.reset(NULL); if (collator_ != NULL) { gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store_), CERT_NAME, SortNameFuncThunk, this, NULL); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store_), CERT_SECURITY_DEVICE, SortDeviceFuncThunk, this, NULL); } gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store_), CERT_NAME, GTK_SORT_ASCENDING); GtkWidget* scroll_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(scroll_window), GTK_SHADOW_ETCHED_IN); gtk_container_add(GTK_CONTAINER(scroll_window), tree_); gtk_box_pack_start(GTK_BOX(vbox_), scroll_window, TRUE, TRUE, 0); GtkWidget* button_box = gtk_hbox_new(FALSE, gtk_util::kControlSpacing); gtk_box_pack_start(GTK_BOX(vbox_), button_box, FALSE, FALSE, 0); view_button_ = gtk_button_new_with_mnemonic( gtk_util::ConvertAcceleratorsFromWindowsStyle( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_VIEW_CERT_BUTTON)).c_str()); gtk_widget_set_sensitive(view_button_, FALSE); g_signal_connect(view_button_, "clicked", G_CALLBACK(OnViewClickedThunk), this); gtk_box_pack_start(GTK_BOX(button_box), view_button_, FALSE, FALSE, 0); // TODO(mattm): Add buttons for import, export, delete, etc } void CertificatePage::PopulateTree(CERTCertList* cert_list) { DCHECK(gtk_tree_model_get_flags(GTK_TREE_MODEL(store_)) & GTK_TREE_MODEL_ITERS_PERSIST); typedef std::map OrgTreeMap; OrgTreeMap org_tree_map; CERTCertListNode* node; for (node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { CERTCertificate* cert = node->cert; psm::CertType type = psm::GetCertType(cert); if (type == type_) { std::string org = Stringize(CERT_GetOrgName(&cert->subject)); if (org.empty()) org = Stringize(CERT_GetCommonName(&cert->subject)); OrgTreeMap::iterator org_tree_map_iter = org_tree_map.find(org); if (org_tree_map_iter == org_tree_map.end()) { GtkTreeIter iter; gtk_tree_store_append(store_, &iter, NULL); gtk_tree_store_set(store_, &iter, CERT_NAME, org.c_str(), -1); org_tree_map_iter = org_tree_map.insert(std::make_pair(org, iter)).first; } std::string name = Stringize(CERT_GetCommonName(&cert->subject)); if (name.empty() && cert->nickname) { name = cert->nickname; // Hack copied from mozilla: Cut off text before first :, which seems to // just be the token name. size_t colon_pos = name.find(':'); if (colon_pos != std::string::npos) name = name.substr(colon_pos + 1); } GtkTreeIter iter; gtk_tree_store_append(store_, &iter, &org_tree_map_iter->second); gtk_tree_store_set(store_, &iter, CERT_NAME, name.c_str(), CERT_SECURITY_DEVICE, psm::GetCertTokenName(cert).c_str(), CERT_SERIAL_NUMBER, Stringize(CERT_Hexify(&cert->serialNumber, TRUE)).c_str(), CERT_ADDRESS, cert->emailAddr, CERT_POINTER, cert, -1); PRTime issued, expires; if (CERT_GetCertTimes(cert, &issued, &expires) == SECSuccess) { gtk_tree_store_set(store_, &iter, CERT_EXPIRES_ON, WideToUTF8(base::TimeFormatShortDateNumeric( base::PRTimeToBaseTime(expires))).c_str(), CERT_EXPIRES_ON_INT, expires, -1); } } } gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_)); } gint CertificatePage::LocaleSortFunc(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b, int col) { gchar* value1 = NULL; gchar* value2 = NULL; gtk_tree_model_get(model, a, col, &value1, -1); gtk_tree_model_get(model, b, col, &value2, -1); if (!value1 || !value2) { if (value1) return 1; if (value2) return -1; return 0; } return l10n_util::CompareStringWithCollator(collator_.get(), UTF8ToWide(value1), UTF8ToWide(value2)); } gint CertificatePage::SortNameFunc(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b) { return LocaleSortFunc(model, a, b, CERT_NAME); } gint CertificatePage::SortDeviceFunc(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b) { return LocaleSortFunc(model, a, b, CERT_SECURITY_DEVICE); } void CertificatePage::OnSelectionChanged(GtkTreeSelection* selection) { CERTCertificate* cert = NULL; GtkTreeIter iter; GtkTreeModel* model; if (gtk_tree_selection_get_selected(selection_, &model, &iter)) gtk_tree_model_get(model, &iter, CERT_POINTER, &cert, -1); gtk_widget_set_sensitive(view_button_, cert ? TRUE : FALSE); } void CertificatePage::OnViewClicked(GtkWidget* button) { GtkTreeIter iter; GtkTreeModel* model; if (!gtk_tree_selection_get_selected(selection_, &model, &iter)) return; CERTCertificate* cert = NULL; gtk_tree_model_get(model, &iter, CERT_POINTER, &cert, -1); if (cert) ShowCertificateViewer(GTK_WINDOW(gtk_widget_get_toplevel(widget())), cert); } //////////////////////////////////////////////////////////////////////////////// // CertificateManager class definition. class CertificateManager { public: explicit CertificateManager(gfx::NativeWindow parent); ~CertificateManager(); void Show(); private: CERTCertList* cert_list_; CertificatePage user_page_; CertificatePage email_page_; CertificatePage server_page_; CertificatePage ca_page_; CertificatePage unknown_page_; GtkWidget* dialog_; }; //////////////////////////////////////////////////////////////////////////////// // CertificateManager implementation. void OnDestroy(GtkDialog* dialog, CertificateManager* cert_manager) { delete cert_manager; } CertificateManager::CertificateManager(gfx::NativeWindow parent) : user_page_(psm::USER_CERT), email_page_(psm::EMAIL_CERT), server_page_(psm::SERVER_CERT), ca_page_(psm::CA_CERT), unknown_page_(psm::UNKNOWN_CERT) { dialog_ = gtk_dialog_new_with_buttons( l10n_util::GetStringUTF8(IDS_CERTIFICATE_MANAGER_TITLE).c_str(), parent, // Non-modal. GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog_)->vbox), gtk_util::kContentAreaSpacing); gtk_window_set_default_size(GTK_WINDOW(dialog_), 600, 440); GtkWidget* notebook = gtk_notebook_new(); gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog_)->vbox), notebook); // TODO(mattm): Remember which page user viewed last. gtk_notebook_append_page( GTK_NOTEBOOK(notebook), user_page_.widget(), gtk_label_new_with_mnemonic( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_PERSONAL_CERTS_TAB_LABEL).c_str())); gtk_notebook_append_page( GTK_NOTEBOOK(notebook), email_page_.widget(), gtk_label_new_with_mnemonic( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_OTHER_PEOPLES_CERTS_TAB_LABEL).c_str())); gtk_notebook_append_page( GTK_NOTEBOOK(notebook), server_page_.widget(), gtk_label_new_with_mnemonic( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_SERVER_CERTS_TAB_LABEL).c_str())); gtk_notebook_append_page( GTK_NOTEBOOK(notebook), ca_page_.widget(), gtk_label_new_with_mnemonic( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_CERT_AUTHORITIES_TAB_LABEL).c_str())); gtk_notebook_append_page( GTK_NOTEBOOK(notebook), unknown_page_.widget(), gtk_label_new_with_mnemonic( l10n_util::GetStringUTF8( IDS_CERT_MANAGER_UNKNOWN_TAB_LABEL).c_str())); cert_list_ = PK11_ListCerts(PK11CertListUnique, NULL); user_page_.PopulateTree(cert_list_); email_page_.PopulateTree(cert_list_); server_page_.PopulateTree(cert_list_); ca_page_.PopulateTree(cert_list_); unknown_page_.PopulateTree(cert_list_); g_signal_connect(dialog_, "response", G_CALLBACK(gtk_widget_destroy), NULL); g_signal_connect(dialog_, "destroy", G_CALLBACK(OnDestroy), this); } CertificateManager::~CertificateManager() { CERT_DestroyCertList(cert_list_); } void CertificateManager::Show() { gtk_widget_show_all(dialog_); } } // namespace void ShowCertificateManager(gfx::NativeWindow parent) { base::EnsureNSSInit(); (new CertificateManager(parent))->Show(); }