summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-09 03:30:02 +0000
committermattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-09 03:30:02 +0000
commitc3deb9a2f5772aaea66cebd864d9e182c2870d62 (patch)
tree866aac92c79850e44690d0e48deeab047d88d87b
parentd75c4e1f5e6d8ae1f55a8a54410a768f10f1a66c (diff)
downloadchromium_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.grd18
-rw-r--r--chrome/browser/gtk/certificate_dialogs.cc260
-rw-r--r--chrome/browser/gtk/certificate_dialogs.h14
-rw-r--r--chrome/browser/gtk/certificate_viewer.cc58
-rwxr-xr-xchrome/chrome_browser.gypi4
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&amp;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'],