1 // Copyright (c) 2012 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/ui/certificate_dialogs.h" 6 7 #include <algorithm> 8 #include <vector> 9 10 #include "base/base64.h" 11 #include "base/bind.h" 12 #include "base/files/file_util.h" 13 #include "base/logging.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "chrome/browser/ui/chrome_select_file_policy.h" 16 #include "chrome/common/net/x509_certificate_model.h" 17 #include "chrome/grit/generated_resources.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "net/base/filename_util.h" 20 #include "ui/base/l10n/l10n_util.h" 21 #include "ui/shell_dialogs/select_file_dialog.h" 22 #include "url/gurl.h" 23 24 using content::BrowserThread; 25 using content::WebContents; 26 27 namespace { 28 29 void WriterCallback(const base::FilePath& path, const std::string& data) { 30 int bytes_written = base::WriteFile(path, data.data(), data.size()); 31 if (bytes_written != static_cast<ssize_t>(data.size())) { 32 LOG(ERROR) << "Writing " << path.value() << " (" 33 << data.size() << "B) returned " << bytes_written; 34 } 35 } 36 37 void WriteFileOnFileThread(const base::FilePath& path, 38 const std::string& data) { 39 BrowserThread::PostTask( 40 BrowserThread::FILE, FROM_HERE, base::Bind(&WriterCallback, path, data)); 41 } 42 43 std::string WrapAt64(const std::string &str) { 44 std::string result; 45 for (size_t i = 0; i < str.size(); i += 64) { 46 result.append(str, i, 64); // Append clamps the len arg internally. 47 result.append("\r\n"); 48 } 49 return result; 50 } 51 52 std::string GetBase64String(net::X509Certificate::OSCertHandle cert) { 53 std::string der_cert; 54 if (!net::X509Certificate::GetDEREncoded(cert, &der_cert)) 55 return std::string(); 56 std::string base64; 57 base::Base64Encode(der_cert, &base64); 58 return "-----BEGIN CERTIFICATE-----\r\n" + 59 WrapAt64(base64) + 60 "-----END CERTIFICATE-----\r\n"; 61 } 62 63 //////////////////////////////////////////////////////////////////////////////// 64 // General utility functions. 65 66 class Exporter : public ui::SelectFileDialog::Listener { 67 public: 68 Exporter(WebContents* web_contents, 69 gfx::NativeWindow parent, 70 net::X509Certificate::OSCertHandles::iterator certs_begin, 71 net::X509Certificate::OSCertHandles::iterator certs_end); 72 virtual ~Exporter(); 73 74 // SelectFileDialog::Listener implemenation. 75 virtual void FileSelected(const base::FilePath& path, 76 int index, void* params) OVERRIDE; 77 virtual void FileSelectionCanceled(void* params) OVERRIDE; 78 private: 79 scoped_refptr<ui::SelectFileDialog> select_file_dialog_; 80 81 // The certificate hierarchy (leaf cert first). 82 net::X509Certificate::OSCertHandles cert_chain_list_; 83 }; 84 85 Exporter::Exporter(WebContents* web_contents, 86 gfx::NativeWindow parent, 87 net::X509Certificate::OSCertHandles::iterator certs_begin, 88 net::X509Certificate::OSCertHandles::iterator certs_end) 89 : select_file_dialog_(ui::SelectFileDialog::Create( 90 this, 91 new ChromeSelectFilePolicy(web_contents))) { 92 DCHECK(certs_begin != certs_end); 93 for (net::X509Certificate::OSCertHandles::iterator i = certs_begin; 94 i != certs_end; 95 ++i) { 96 cert_chain_list_.push_back(net::X509Certificate::DupOSCertHandle(*i)); 97 } 98 99 // TODO(mattm): should this default to some directory? 100 // Maybe SavePackage::GetSaveDirPreference? (Except that it's private.) 101 std::string cert_title = x509_certificate_model::GetTitle(*certs_begin); 102 base::FilePath suggested_path = 103 net::GenerateFileName(GURL::EmptyGURL(), // url 104 std::string(), // content_disposition 105 std::string(), // referrer_charset 106 cert_title, // suggested_name 107 std::string(), // mime_type 108 "certificate"); // default_name 109 110 ShowCertSelectFileDialog(select_file_dialog_.get(), 111 ui::SelectFileDialog::SELECT_SAVEAS_FILE, 112 suggested_path, 113 parent, 114 NULL); 115 } 116 117 Exporter::~Exporter() { 118 // There may be pending file dialogs, we need to tell them that we've gone 119 // away so they don't try and call back to us. 120 if (select_file_dialog_.get()) 121 select_file_dialog_->ListenerDestroyed(); 122 123 std::for_each(cert_chain_list_.begin(), 124 cert_chain_list_.end(), 125 &net::X509Certificate::FreeOSCertHandle); 126 } 127 128 void Exporter::FileSelected(const base::FilePath& path, int index, 129 void* params) { 130 std::string data; 131 switch (index) { 132 case 2: 133 for (size_t i = 0; i < cert_chain_list_.size(); ++i) 134 data += GetBase64String(cert_chain_list_[i]); 135 break; 136 case 3: 137 net::X509Certificate::GetDEREncoded(cert_chain_list_[0], &data); 138 break; 139 case 4: 140 data = x509_certificate_model::GetCMSString(cert_chain_list_, 0, 1); 141 break; 142 case 5: 143 data = x509_certificate_model::GetCMSString( 144 cert_chain_list_, 0, cert_chain_list_.size()); 145 break; 146 case 1: 147 default: 148 data = GetBase64String(cert_chain_list_[0]); 149 break; 150 } 151 152 if (!data.empty()) 153 WriteFileOnFileThread(path, data); 154 155 delete this; 156 } 157 158 void Exporter::FileSelectionCanceled(void* params) { 159 delete this; 160 } 161 162 } // namespace 163 164 void ShowCertSelectFileDialog(ui::SelectFileDialog* select_file_dialog, 165 ui::SelectFileDialog::Type type, 166 const base::FilePath& suggested_path, 167 gfx::NativeWindow parent, 168 void* params) { 169 ui::SelectFileDialog::FileTypeInfo file_type_info; 170 file_type_info.extensions.resize(5); 171 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("pem")); 172 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("crt")); 173 file_type_info.extension_description_overrides.push_back( 174 l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_BASE64)); 175 file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("pem")); 176 file_type_info.extensions[1].push_back(FILE_PATH_LITERAL("crt")); 177 file_type_info.extension_description_overrides.push_back( 178 l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_BASE64_CHAIN)); 179 file_type_info.extensions[2].push_back(FILE_PATH_LITERAL("der")); 180 file_type_info.extension_description_overrides.push_back( 181 l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_DER)); 182 file_type_info.extensions[3].push_back(FILE_PATH_LITERAL("p7c")); 183 file_type_info.extension_description_overrides.push_back( 184 l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_PKCS7)); 185 file_type_info.extensions[4].push_back(FILE_PATH_LITERAL("p7c")); 186 file_type_info.extension_description_overrides.push_back( 187 l10n_util::GetStringUTF16(IDS_CERT_EXPORT_TYPE_PKCS7_CHAIN)); 188 file_type_info.include_all_files = true; 189 select_file_dialog->SelectFile( 190 type, base::string16(), 191 suggested_path, &file_type_info, 192 1, // 1-based index for |file_type_info.extensions| to specify default. 193 FILE_PATH_LITERAL("crt"), 194 parent, params); 195 } 196 197 void ShowCertExportDialog(WebContents* web_contents, 198 gfx::NativeWindow parent, 199 const scoped_refptr<net::X509Certificate>& cert) { 200 net::X509Certificate::OSCertHandles cert_chain; 201 cert_chain.push_back(cert->os_cert_handle()); 202 const net::X509Certificate::OSCertHandles& certs = 203 cert->GetIntermediateCertificates(); 204 cert_chain.insert(cert_chain.end(), certs.begin(), certs.end()); 205 new Exporter(web_contents, parent, cert_chain.begin(), cert_chain.end()); 206 } 207 208 void ShowCertExportDialog( 209 content::WebContents* web_contents, 210 gfx::NativeWindow parent, 211 net::X509Certificate::OSCertHandles::iterator certs_begin, 212 net::X509Certificate::OSCertHandles::iterator certs_end) { 213 new Exporter(web_contents, parent, certs_begin, certs_end); 214 } 215