diff options
author | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-09 03:30:02 +0000 |
---|---|---|
committer | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-09 03:30:02 +0000 |
commit | c3deb9a2f5772aaea66cebd864d9e182c2870d62 (patch) | |
tree | 866aac92c79850e44690d0e48deeab047d88d87b | |
parent | d75c4e1f5e6d8ae1f55a8a54410a768f10f1a66c (diff) | |
download | chromium_src-c3deb9a2f5772aaea66cebd864d9e182c2870d62.zip chromium_src-c3deb9a2f5772aaea66cebd864d9e182c2870d62.tar.gz chromium_src-c3deb9a2f5772aaea66cebd864d9e182c2870d62.tar.bz2 |
Linux: Add export option to certificate viewer.
BUG=18119
TEST=Export some certs, compare files with exports from firefox.
Review URL: http://codereview.chromium.org/669137
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40993 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/generated_resources.grd | 18 | ||||
-rw-r--r-- | chrome/browser/gtk/certificate_dialogs.cc | 260 | ||||
-rw-r--r-- | chrome/browser/gtk/certificate_dialogs.h | 14 | ||||
-rw-r--r-- | chrome/browser/gtk/certificate_viewer.cc | 58 | ||||
-rwxr-xr-x | chrome/chrome_browser.gypi | 4 |
5 files changed, 347 insertions, 7 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 4e48382c..06bb733 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2684,6 +2684,24 @@ each locale. --> <message name="IDS_CERT_DETAILS_CERTIFICATE_SIG_VALUE" desc="The label of the Certificate Signature Value element in the details page of the certificate info dialog."> Certificate Signature Value </message> + <message name="IDS_CERT_DETAILS_EXPORT_CERTIFICATE" desc="The label of the button to export the selected certificate"> + E&xport... + </message> + <message name="IDS_CERT_EXPORT_TYPE_BASE64" desc="The description of saving a single certificate in base64 encoding."> + Base64-encoded ASCII, single certificate + </message> + <message name="IDS_CERT_EXPORT_TYPE_BASE64_CHAIN" desc="The description of saving a certificate chain in base64 encoding."> + Base64-encoded ASCII, certificate chain + </message> + <message name="IDS_CERT_EXPORT_TYPE_DER" desc="The description of saving a single certificate in DER encoding."> + DER-encoded binary, single certificate + </message> + <message name="IDS_CERT_EXPORT_TYPE_PKCS7" desc="The description of saving a single certificate in PKCS #7 encoding."> + PKCS #7, single certificate + </message> + <message name="IDS_CERT_EXPORT_TYPE_PKCS7_CHAIN" desc="The description of saving a single certificate in PKCS #7 encoding."> + PKCS #7, certificate chain + </message> <message translateable="false" name="IDS_CERT_OID_AVA_COMMON_NAME" desc=""> CN diff --git a/chrome/browser/gtk/certificate_dialogs.cc b/chrome/browser/gtk/certificate_dialogs.cc new file mode 100644 index 0000000..e181ad4 --- /dev/null +++ b/chrome/browser/gtk/certificate_dialogs.cc @@ -0,0 +1,260 @@ +// 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_dialogs.h" + +#include <cms.h> +#include <gtk/gtk.h> + +#include <vector> + +#include "app/l10n_util.h" +#include "base/base64.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/shell_dialogs.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 { + +//////////////////////////////////////////////////////////////////////////////// +// General utility functions. + +class Writer : public Task { + public: + Writer(const FilePath& path, const std::string& data) + : path_(path), data_(data) { + } + + virtual void Run() { + int bytes_written = file_util::WriteFile(path_, data_.data(), data_.size()); + if (bytes_written != static_cast<ssize_t>(data_.size())) { + LOG(ERROR) << "Writing " << path_.value() << " (" + << data_.size() << "B) returned " << bytes_written; + } + } + private: + FilePath path_; + std::string data_; +}; + +void WriteFileOnFileThread(const FilePath& path, const std::string& data) { + ChromeThread::PostTask( + ChromeThread::FILE, FROM_HERE, new Writer(path, data)); +} + +//////////////////////////////////////////////////////////////////////////////// +// NSS certificate export functions. + +class FreeNSSCMSMessage { + public: + inline void operator()(NSSCMSMessage* x) const { + NSS_CMSMessage_Destroy(x); + } +}; +typedef scoped_ptr_malloc<NSSCMSMessage, FreeNSSCMSMessage> + ScopedNSSCMSMessage; + +class FreeNSSCMSSignedData { + public: + inline void operator()(NSSCMSSignedData* x) const { + NSS_CMSSignedData_Destroy(x); + } +}; +typedef scoped_ptr_malloc<NSSCMSSignedData, FreeNSSCMSSignedData> + ScopedNSSCMSSignedData; + +std::string GetDerString(CERTCertificate* cert) { + return std::string(reinterpret_cast<const char*>(cert->derCert.data), + cert->derCert.len); +} + +std::string WrapAt64(const std::string &str) { + std::string result; + for (size_t i = 0; i < str.size(); i += 64) { + result.append(str, i, 64); // Append clamps the len arg internally. + result.append("\r\n"); + } + return result; +} + +std::string GetBase64String(CERTCertificate* cert) { + std::string base64; + if (!base::Base64Encode(GetDerString(cert), &base64)) { + LOG(ERROR) << "base64 encoding error"; + return ""; + } + return "-----BEGIN CERTIFICATE-----\r\n" + + WrapAt64(base64) + + "-----END CERTIFICATE-----\r\n"; +} + +std::string GetCMSString(std::vector<CERTCertificate*> cert_chain, size_t start, + size_t end) { + ScopedPRArenaPool arena(PORT_NewArena(1024)); + CHECK(arena.get()); + + ScopedNSSCMSMessage message(NSS_CMSMessage_Create(arena.get())); + CHECK(message.get()); + + // First, create SignedData with the certificate only (no chain). + ScopedNSSCMSSignedData signed_data( + NSS_CMSSignedData_CreateCertsOnly(message.get(), cert_chain[start], + PR_FALSE)); + if (!signed_data.get()) { + LOG(ERROR) << "NSS_CMSSignedData_Create failed"; + return ""; + } + // Add the rest of the chain (if any). + for (size_t i = start + 1; i < end; ++i) { + if (NSS_CMSSignedData_AddCertificate(signed_data.get(), cert_chain[i]) != + SECSuccess) { + LOG(ERROR) << "NSS_CMSSignedData_AddCertificate failed on " << i; + return ""; + } + } + + NSSCMSContentInfo *cinfo = NSS_CMSMessage_GetContentInfo(message.get()); + if (NSS_CMSContentInfo_SetContent_SignedData( + message.get(), cinfo, signed_data.get()) == SECSuccess) { + signed_data.release(); + } else { + LOG(ERROR) << "NSS_CMSMessage_GetContentInfo failed"; + return ""; + } + + SECItem cert_p7 = { siBuffer, NULL, 0 }; + NSSCMSEncoderContext *ecx = NSS_CMSEncoder_Start(message.get(), NULL, NULL, + &cert_p7, arena.get(), NULL, + NULL, NULL, NULL, NULL, + NULL); + if (!ecx) { + LOG(ERROR) << "NSS_CMSEncoder_Start failed"; + return ""; + } + + if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) { + LOG(ERROR) << "NSS_CMSEncoder_Finish failed"; + return ""; + } + + return std::string(reinterpret_cast<const char*>(cert_p7.data), cert_p7.len); +} + +//////////////////////////////////////////////////////////////////////////////// +// General utility functions. + +class Exporter : public SelectFileDialog::Listener { + public: + Exporter(gfx::NativeWindow parent, CERTCertificate* cert); + ~Exporter(); + + // SelectFileDialog::Listener implemenation. + virtual void FileSelected(const FilePath& path, + int index, void* params); + virtual void FileSelectionCanceled(void* params); + private: + scoped_refptr<SelectFileDialog> select_file_dialog_; + + // The certificate hierarchy (leaf cert first). + CERTCertList* cert_chain_list_; + // The same contents of cert_chain_list_ in a vector for easier access. + std::vector<CERTCertificate*> cert_chain_; +}; + +Exporter::Exporter(gfx::NativeWindow parent, CERTCertificate* cert) + : select_file_dialog_(SelectFileDialog::Create(this)) { + cert_chain_list_ = CERT_GetCertChainFromCert(cert, PR_Now(), + certUsageSSLServer); + DCHECK(cert_chain_list_); + CERTCertListNode* node; + for (node = CERT_LIST_HEAD(cert_chain_list_); + !CERT_LIST_END(node, cert_chain_list_); + node = CERT_LIST_NEXT(node)) { + cert_chain_.push_back(node->cert); + } + + // TODO(mattm): should this default to some directory? + // Maybe SavePackage::GetSaveDirPreference? (Except that it's private.) + FilePath suggested_path("certificate"); + std::string cert_title = psm::GetCertTitle(cert); + if (!cert_title.empty()) + suggested_path = FilePath(cert_title); + + SelectFileDialog::FileTypeInfo file_type_info; + file_type_info.extensions.resize(5); + file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("pem")); + file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("crt")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_BASE64)); + file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("pem")); + file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("crt")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_BASE64_CHAIN)); + file_type_info.extensions[2].push_back(FILE_PATH_LITERAL("der")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_DER)); + file_type_info.extensions[3].push_back(FILE_PATH_LITERAL("p7c")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_PKCS7)); + file_type_info.extensions[4].push_back(FILE_PATH_LITERAL("p7c")); + file_type_info.extension_description_overrides.push_back( + l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_PKCS7_CHAIN)); + file_type_info.include_all_files = true; + select_file_dialog_->SelectFile( + SelectFileDialog::SELECT_SAVEAS_FILE, string16(), + suggested_path, &file_type_info, 1, + FILE_PATH_LITERAL("crt"), parent, + NULL); +} + +Exporter::~Exporter() { + CERT_DestroyCertList(cert_chain_list_); +} + +void Exporter::FileSelected(const FilePath& path, int index, void* params) { + std::string data; + switch (index) { + case 2: + for (size_t i = 0; i < cert_chain_.size(); ++i) + data += GetBase64String(cert_chain_[i]); + break; + case 3: + data = GetDerString(cert_chain_[0]); + break; + case 4: + data = GetCMSString(cert_chain_, 0, 1); + break; + case 5: + data = GetCMSString(cert_chain_, 0, cert_chain_.size()); + break; + case 1: + default: + data = GetBase64String(cert_chain_[0]); + break; + } + + if (!data.empty()) + WriteFileOnFileThread(path, data); + + delete this; +} + +void Exporter::FileSelectionCanceled(void* params) { + delete this; +} + +} // namespace + +void ShowCertExportDialog(gfx::NativeWindow parent, CERTCertificate* cert) { + new Exporter(parent, cert); +} diff --git a/chrome/browser/gtk/certificate_dialogs.h b/chrome/browser/gtk/certificate_dialogs.h new file mode 100644 index 0000000..1890a4a --- /dev/null +++ b/chrome/browser/gtk/certificate_dialogs.h @@ -0,0 +1,14 @@ +// 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. + +#ifndef CHROME_BROWSER_GTK_CERTIFICATE_DIALOGS_H_ +#define CHROME_BROWSER_GTK_CERTIFICATE_DIALOGS_H_ + +#include <cert.h> + +#include "chrome/browser/shell_dialogs.h" + +void ShowCertExportDialog(gfx::NativeWindow parent, CERTCertificate* cert); + +#endif // CHROME_BROWSER_GTK_CERTIFICATE_DIALOGS_H_ diff --git a/chrome/browser/gtk/certificate_viewer.cc b/chrome/browser/gtk/certificate_viewer.cc index 8df61b4..e6caff7 100644 --- a/chrome/browser/gtk/certificate_viewer.cc +++ b/chrome/browser/gtk/certificate_viewer.cc @@ -10,14 +10,17 @@ #include <sechash.h> #include <algorithm> +#include <vector> #include "app/l10n_util.h" #include "base/i18n/time_formatting.h" #include "base/nss_util.h" +#include "base/scoped_ptr.h" #include "base/string_util.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/browser/cert_store.h" +#include "chrome/browser/gtk/certificate_dialogs.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" @@ -125,6 +128,7 @@ class CertificateViewer { enum { HIERARCHY_NAME, HIERARCHY_OBJECT, + HIERARCHY_INDEX, HIERARCHY_COLUMNS }; @@ -153,6 +157,9 @@ class CertificateViewer { static void OnFieldsSelectionChanged(GtkTreeSelection* selection, CertificateViewer* viewer); + // Callback for export button. + static void OnExportClicked(GtkButton *button, CertificateViewer* viewer); + // The certificate hierarchy (leaf cert first). CERTCertList* cert_chain_list_; // The same contents of cert_chain_list_ in a vector for easier access. @@ -163,8 +170,10 @@ class CertificateViewer { GtkWidget* notebook_; GtkWidget* general_page_vbox_; GtkWidget* details_page_vbox_; + GtkTreeSelection* hierarchy_selection_; GtkWidget* fields_tree_; GtkTextBuffer* field_value_buffer_; + GtkWidget* export_button_; DISALLOW_COPY_AND_ASSIGN(CertificateViewer); }; @@ -341,14 +350,16 @@ void CertificateViewer::FillHierarchyStore(GtkTreeStore* hierarchy_store, GtkTreeIter parent; GtkTreeIter* parent_ptr = NULL; GtkTreeIter iter; + gint index = cert_chain_.size() - 1; for (CertificateVector::const_reverse_iterator i = cert_chain_.rbegin(); - i != cert_chain_.rend(); ++i) { + i != cert_chain_.rend(); ++i, --index) { gtk_tree_store_append(hierarchy_store, &iter, parent_ptr); GtkTreeStore* fields_store = CreateFieldsTreeStore(*i); gtk_tree_store_set( hierarchy_store, &iter, HIERARCHY_NAME, psm::GetCertTitle(*i).c_str(), HIERARCHY_OBJECT, fields_store, + HIERARCHY_INDEX, index, -1); g_object_unref(fields_store); parent = iter; @@ -545,7 +556,8 @@ void CertificateViewer::InitDetailsPage() { GtkTreeStore* hierarchy_store = gtk_tree_store_new(HIERARCHY_COLUMNS, G_TYPE_STRING, - G_TYPE_OBJECT); + G_TYPE_OBJECT, + G_TYPE_INT); GtkTreeIter hierarchy_leaf_iter; FillHierarchyStore(hierarchy_store, &hierarchy_leaf_iter); GtkWidget* hierarchy_tree = gtk_tree_view_new_with_model( @@ -557,10 +569,10 @@ void CertificateViewer::InitDetailsPage() { "text", HIERARCHY_NAME, NULL)); gtk_tree_view_expand_all(GTK_TREE_VIEW(hierarchy_tree)); - GtkTreeSelection* hierarchy_selection = gtk_tree_view_get_selection( + hierarchy_selection_ = gtk_tree_view_get_selection( GTK_TREE_VIEW(hierarchy_tree)); - gtk_tree_selection_set_mode(hierarchy_selection, GTK_SELECTION_SINGLE); - g_signal_connect(hierarchy_selection, "changed", + gtk_tree_selection_set_mode(hierarchy_selection_, GTK_SELECTION_SINGLE); + g_signal_connect(hierarchy_selection_, "changed", G_CALLBACK(OnHierarchySelectionChanged), this); GtkWidget* hierarchy_scroll_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(hierarchy_scroll_window), @@ -633,10 +645,20 @@ void CertificateViewer::InitDetailsPage() { gtk_widget_modify_font(field_value_view, font_desc); pango_font_description_free(font_desc); - // TODO(mattm): export certificate button. + GtkWidget* export_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(details_page_vbox_), export_hbox, + FALSE, FALSE, 0); + export_button_ = gtk_button_new_with_mnemonic( + gtk_util::ConvertAcceleratorsFromWindowsStyle( + l10n_util::GetStringUTF8( + IDS_CERT_DETAILS_EXPORT_CERTIFICATE)).c_str()); + g_signal_connect(export_button_, "clicked", + G_CALLBACK(OnExportClicked), this); + gtk_box_pack_start(GTK_BOX(export_hbox), export_button_, + FALSE, FALSE, 0); // Select the initial certificate in the hierarchy. - gtk_tree_selection_select_iter(hierarchy_selection, &hierarchy_leaf_iter); + gtk_tree_selection_select_iter(hierarchy_selection_, &hierarchy_leaf_iter); } // static @@ -650,8 +672,10 @@ void CertificateViewer::OnHierarchySelectionChanged( gtk_tree_view_set_model(GTK_TREE_VIEW(viewer->fields_tree_), GTK_TREE_MODEL(fields_store)); gtk_tree_view_expand_all(GTK_TREE_VIEW(viewer->fields_tree_)); + gtk_widget_set_sensitive(viewer->export_button_, TRUE); } else { gtk_tree_view_set_model(GTK_TREE_VIEW(viewer->fields_tree_), NULL); + gtk_widget_set_sensitive(viewer->export_button_, FALSE); } } @@ -674,6 +698,26 @@ void CertificateViewer::OnFieldsSelectionChanged(GtkTreeSelection* selection, } } +// static +void CertificateViewer::OnExportClicked(GtkButton *button, + CertificateViewer* viewer) { + GtkTreeIter iter; + GtkTreeModel* model; + if (!gtk_tree_selection_get_selected(viewer->hierarchy_selection_, &model, + &iter)) + return; + gint cert_index = -1; + gtk_tree_model_get(model, &iter, HIERARCHY_INDEX, &cert_index, -1); + + if (cert_index < 0) { + NOTREACHED(); + return; + } + + ShowCertExportDialog(GTK_WINDOW(viewer->dialog_), + viewer->cert_chain_[cert_index]); +} + void CertificateViewer::Show() { gtk_widget_show_all(dialog_); } diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index e188504..ddee32d 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1077,6 +1077,8 @@ 'browser/gtk/cairo_cached_surface.h', 'browser/gtk/certificate_manager.cc', 'browser/gtk/certificate_manager.h', + 'browser/gtk/certificate_dialogs.cc', + 'browser/gtk/certificate_dialogs.h', 'browser/gtk/certificate_viewer.cc', 'browser/gtk/certificate_viewer.h', 'browser/gtk/clear_browsing_data_dialog_gtk.cc', @@ -2761,6 +2763,8 @@ ['include', '^browser/gtk/cairo_cached_surface.h'], ['include', '^browser/gtk/clear_browsing_data_dialog_gtk.cc'], ['include', '^browser/gtk/clear_browsing_data_dialog_gtk.h'], + ['include', '^browser/gtk/certificate_dialogs.cc'], + ['include', '^browser/gtk/certificate_dialogs.h'], ['include', '^browser/gtk/certificate_viewer.cc'], ['include', '^browser/gtk/certificate_viewer.h'], ['include', '^browser/gtk/constrained_window_gtk.cc'], |