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