| // Copyright (c) 2012 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/ui/certificate_dialogs.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "chrome/browser/ui/chrome_select_file_policy.h" |
| #include "chrome/common/net/x509_certificate_model.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/filename_util.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/shell_dialogs/select_file_dialog.h" |
| #include "url/gurl.h" |
| |
| using content::BrowserThread; |
| using content::WebContents; |
| |
| namespace { |
| |
| void WriterCallback(const base::FilePath& path, const std::string& data) { |
| int bytes_written = base::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; |
| } |
| } |
| |
| void WriteFileOnFileThread(const base::FilePath& path, |
| const std::string& data) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, base::Bind(&WriterCallback, path, data)); |
| } |
| |
| 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(net::X509Certificate::OSCertHandle cert) { |
| std::string der_cert; |
| if (!net::X509Certificate::GetDEREncoded(cert, &der_cert)) |
| return std::string(); |
| std::string base64; |
| base::Base64Encode(der_cert, &base64); |
| return "-----BEGIN CERTIFICATE-----\r\n" + |
| WrapAt64(base64) + |
| "-----END CERTIFICATE-----\r\n"; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // General utility functions. |
| |
| class Exporter : public ui::SelectFileDialog::Listener { |
| public: |
| Exporter(WebContents* web_contents, |
| gfx::NativeWindow parent, |
| net::X509Certificate::OSCertHandles::iterator certs_begin, |
| net::X509Certificate::OSCertHandles::iterator certs_end); |
| ~Exporter() override; |
| |
| // SelectFileDialog::Listener implemenation. |
| void FileSelected(const base::FilePath& path, |
| int index, |
| void* params) override; |
| void FileSelectionCanceled(void* params) override; |
| |
| private: |
| scoped_refptr<ui::SelectFileDialog> select_file_dialog_; |
| |
| // The certificate hierarchy (leaf cert first). |
| net::X509Certificate::OSCertHandles cert_chain_list_; |
| }; |
| |
| Exporter::Exporter(WebContents* web_contents, |
| gfx::NativeWindow parent, |
| net::X509Certificate::OSCertHandles::iterator certs_begin, |
| net::X509Certificate::OSCertHandles::iterator certs_end) |
| : select_file_dialog_(ui::SelectFileDialog::Create( |
| this, |
| new ChromeSelectFilePolicy(web_contents))) { |
| DCHECK(certs_begin != certs_end); |
| for (net::X509Certificate::OSCertHandles::iterator i = certs_begin; |
| i != certs_end; |
| ++i) { |
| cert_chain_list_.push_back(net::X509Certificate::DupOSCertHandle(*i)); |
| } |
| |
| // TODO(mattm): should this default to some directory? |
| // Maybe SavePackage::GetSaveDirPreference? (Except that it's private.) |
| std::string cert_title = x509_certificate_model::GetTitle(*certs_begin); |
| base::FilePath suggested_path = |
| net::GenerateFileName(GURL::EmptyGURL(), // url |
| std::string(), // content_disposition |
| std::string(), // referrer_charset |
| cert_title, // suggested_name |
| std::string(), // mime_type |
| "certificate"); // default_name |
| |
| ShowCertSelectFileDialog(select_file_dialog_.get(), |
| ui::SelectFileDialog::SELECT_SAVEAS_FILE, |
| suggested_path, |
| parent, |
| NULL); |
| } |
| |
| Exporter::~Exporter() { |
| // There may be pending file dialogs, we need to tell them that we've gone |
| // away so they don't try and call back to us. |
| if (select_file_dialog_.get()) |
| select_file_dialog_->ListenerDestroyed(); |
| |
| std::for_each(cert_chain_list_.begin(), |
| cert_chain_list_.end(), |
| &net::X509Certificate::FreeOSCertHandle); |
| } |
| |
| void Exporter::FileSelected(const base::FilePath& path, int index, |
| void* params) { |
| std::string data; |
| switch (index) { |
| case 2: |
| for (size_t i = 0; i < cert_chain_list_.size(); ++i) |
| data += GetBase64String(cert_chain_list_[i]); |
| break; |
| case 3: |
| net::X509Certificate::GetDEREncoded(cert_chain_list_[0], &data); |
| break; |
| case 4: |
| data = x509_certificate_model::GetCMSString(cert_chain_list_, 0, 1); |
| break; |
| case 5: |
| data = x509_certificate_model::GetCMSString( |
| cert_chain_list_, 0, cert_chain_list_.size()); |
| break; |
| case 1: |
| default: |
| data = GetBase64String(cert_chain_list_[0]); |
| break; |
| } |
| |
| if (!data.empty()) |
| WriteFileOnFileThread(path, data); |
| |
| delete this; |
| } |
| |
| void Exporter::FileSelectionCanceled(void* params) { |
| delete this; |
| } |
| |
| } // namespace |
| |
| void ShowCertSelectFileDialog(ui::SelectFileDialog* select_file_dialog, |
| ui::SelectFileDialog::Type type, |
| const base::FilePath& suggested_path, |
| gfx::NativeWindow parent, |
| void* params) { |
| ui::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( |
| type, base::string16(), |
| suggested_path, &file_type_info, |
| 1, // 1-based index for |file_type_info.extensions| to specify default. |
| FILE_PATH_LITERAL("crt"), |
| parent, params); |
| } |
| |
| void ShowCertExportDialog(WebContents* web_contents, |
| gfx::NativeWindow parent, |
| const scoped_refptr<net::X509Certificate>& cert) { |
| net::X509Certificate::OSCertHandles cert_chain; |
| cert_chain.push_back(cert->os_cert_handle()); |
| const net::X509Certificate::OSCertHandles& certs = |
| cert->GetIntermediateCertificates(); |
| cert_chain.insert(cert_chain.end(), certs.begin(), certs.end()); |
| new Exporter(web_contents, parent, cert_chain.begin(), cert_chain.end()); |
| } |
| |
| void ShowCertExportDialog( |
| content::WebContents* web_contents, |
| gfx::NativeWindow parent, |
| net::X509Certificate::OSCertHandles::iterator certs_begin, |
| net::X509Certificate::OSCertHandles::iterator certs_end) { |
| new Exporter(web_contents, parent, certs_begin, certs_end); |
| } |