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/webui/certificate_viewer_webui.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/i18n/time_formatting.h"
13 #include "base/json/json_writer.h"
14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/certificate_viewer.h"
22 #include "chrome/browser/platform_util.h"
23 #include "chrome/browser/ui/certificate_dialogs.h"
24 #include "chrome/browser/ui/webui/certificate_viewer_ui.h"
25 #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
26 #include "chrome/common/net/x509_certificate_model_nss.h"
27 #include "chrome/common/url_constants.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "components/strings/grit/components_strings.h"
30 #include "content/public/browser/host_zoom_map.h"
31 #include "content/public/browser/web_contents.h"
32 #include "net/cert/x509_util_nss.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/gfx/geometry/size.h"
35
36 using content::WebContents;
37 using content::WebUIMessageHandler;
38
39 namespace {
40
41 // Helper class for building a Value representation of a certificate. The class
42 // gathers data for a single node of the representation tree and builds a
43 // DictionaryValue out of that.
44 class CertNodeBuilder {
45 public:
46 // Starts the node with "label" set to |label|.
47 explicit CertNodeBuilder(base::StringPiece label);
48
49 // Convenience version: Converts |label_id| to the corresponding resource
50 // string, then delegates to the other constructor.
51 explicit CertNodeBuilder(int label_id);
52
53 // Builder methods all return |*this| so that they can be chained in single
54 // expressions.
55
56 // Sets the "payload.val" field. Call this at most once.
57 CertNodeBuilder& Payload(base::StringPiece payload);
58
59 // Adds |child| in the list keyed "children". Can be called multiple times.
60 CertNodeBuilder& Child(std::unique_ptr<base::DictionaryValue> child);
61
62 // Similar to Child, but if the argument is null, then this does not add
63 // anything.
64 CertNodeBuilder& ChildIfNotNull(std::unique_ptr<base::DictionaryValue> child);
65
66 // Creates a DictionaryValue representation of the collected information. Only
67 // call this once.
68 std::unique_ptr<base::DictionaryValue> Build();
69
70 private:
71 base::DictionaryValue node_;
72 base::ListValue children_;
73 // |built_| is false until Build() is called. Once it is |true|, |node_| and
74 // |children_| are no longer valid for use.
75 bool built_ = false;
76
77 DISALLOW_COPY_AND_ASSIGN(CertNodeBuilder);
78 };
79
CertNodeBuilder(base::StringPiece label)80 CertNodeBuilder::CertNodeBuilder(base::StringPiece label) {
81 node_.SetString("label", label);
82 }
83
CertNodeBuilder(int label_id)84 CertNodeBuilder::CertNodeBuilder(int label_id)
85 : CertNodeBuilder(l10n_util::GetStringUTF8(label_id)) {}
86
Payload(base::StringPiece payload)87 CertNodeBuilder& CertNodeBuilder::Payload(base::StringPiece payload) {
88 DCHECK(!node_.HasKey("payload.val"));
89 node_.SetString("payload.val", payload);
90 return *this;
91 }
92
Child(std::unique_ptr<base::DictionaryValue> child)93 CertNodeBuilder& CertNodeBuilder::Child(
94 std::unique_ptr<base::DictionaryValue> child) {
95 children_.Append(std::move(child));
96 return *this;
97 }
98
ChildIfNotNull(std::unique_ptr<base::DictionaryValue> child)99 CertNodeBuilder& CertNodeBuilder::ChildIfNotNull(
100 std::unique_ptr<base::DictionaryValue> child) {
101 if (child)
102 return Child(std::move(child));
103 return *this;
104 }
105
Build()106 std::unique_ptr<base::DictionaryValue> CertNodeBuilder::Build() {
107 DCHECK(!built_);
108 if (!children_.empty()) {
109 node_.SetKey("children", std::move(children_));
110 }
111 built_ = true;
112 return std::make_unique<base::DictionaryValue>(std::move(node_));
113 }
114
115 } // namespace
116
117 // Shows a certificate using the WebUI certificate viewer.
ShowCertificateViewer(WebContents * web_contents,gfx::NativeWindow parent,net::X509Certificate * cert)118 void ShowCertificateViewer(WebContents* web_contents,
119 gfx::NativeWindow parent,
120 net::X509Certificate* cert) {
121 net::ScopedCERTCertificateList nss_certs =
122 net::x509_util::CreateCERTCertificateListFromX509Certificate(cert);
123 if (nss_certs.empty())
124 return;
125
126 CertificateViewerDialog::ShowConstrained(std::move(nss_certs), web_contents,
127 parent);
128 }
129
130 ////////////////////////////////////////////////////////////////////////////////
131 // CertificateViewerDialog
132
133 // static
ShowConstrained(net::ScopedCERTCertificateList certs,WebContents * web_contents,gfx::NativeWindow parent)134 CertificateViewerDialog* CertificateViewerDialog::ShowConstrained(
135 net::ScopedCERTCertificateList certs,
136 WebContents* web_contents,
137 gfx::NativeWindow parent) {
138 CertificateViewerDialog* dialog_ptr =
139 new CertificateViewerDialog(std::move(certs));
140 auto dialog = base::WrapUnique(dialog_ptr);
141
142 // TODO(bshe): UI tweaks needed for Aura HTML Dialog, such as adding padding
143 // on the title for Aura ConstrainedWebDialogUI.
144 dialog_ptr->delegate_ = ShowConstrainedWebDialog(
145 web_contents->GetBrowserContext(), std::move(dialog), web_contents);
146
147 // Clear the zoom level for the dialog so that it is not affected by the page
148 // zoom setting.
149 content::WebContents* dialog_web_contents =
150 dialog_ptr->delegate_->GetWebContents();
151 const GURL dialog_url = dialog_ptr->GetDialogContentURL();
152 content::HostZoomMap::Get(dialog_web_contents->GetSiteInstance())
153 ->SetZoomLevelForHostAndScheme(dialog_url.scheme(), dialog_url.host(), 0);
154 return dialog_ptr; // For tests.
155 }
156
GetNativeWebContentsModalDialog()157 gfx::NativeWindow CertificateViewerDialog::GetNativeWebContentsModalDialog() {
158 return delegate_->GetNativeDialog();
159 }
160
CertificateViewerDialog(net::ScopedCERTCertificateList certs)161 CertificateViewerDialog::CertificateViewerDialog(
162 net::ScopedCERTCertificateList certs)
163 : nss_certs_(std::move(certs)) {
164 // Construct the dialog title from the certificate.
165 title_ = l10n_util::GetStringFUTF16(
166 IDS_CERT_INFO_DIALOG_TITLE,
167 base::UTF8ToUTF16(
168 x509_certificate_model::GetTitle(nss_certs_.front().get())));
169 }
170
171 CertificateViewerDialog::~CertificateViewerDialog() = default;
172
GetDialogModalType() const173 ui::ModalType CertificateViewerDialog::GetDialogModalType() const {
174 return ui::MODAL_TYPE_NONE;
175 }
176
GetDialogTitle() const177 base::string16 CertificateViewerDialog::GetDialogTitle() const {
178 return title_;
179 }
180
GetDialogContentURL() const181 GURL CertificateViewerDialog::GetDialogContentURL() const {
182 return GURL(chrome::kChromeUICertificateViewerURL);
183 }
184
GetWebUIMessageHandlers(std::vector<WebUIMessageHandler * > * handlers) const185 void CertificateViewerDialog::GetWebUIMessageHandlers(
186 std::vector<WebUIMessageHandler*>* handlers) const {
187 handlers->push_back(new CertificateViewerDialogHandler(
188 const_cast<CertificateViewerDialog*>(this),
189 net::x509_util::DupCERTCertificateList(nss_certs_)));
190 }
191
GetDialogSize(gfx::Size * size) const192 void CertificateViewerDialog::GetDialogSize(gfx::Size* size) const {
193 const int kDefaultWidth = 544;
194 const int kDefaultHeight = 628;
195 size->SetSize(kDefaultWidth, kDefaultHeight);
196 }
197
GetDialogArgs() const198 std::string CertificateViewerDialog::GetDialogArgs() const {
199 std::string data;
200
201 // Certificate information. The keys in this dictionary's general key
202 // correspond to the IDs in the Html page.
203 base::DictionaryValue cert_info;
204 CERTCertificate* cert_hnd = nss_certs_.front().get();
205
206 // Certificate usage.
207 std::vector<std::string> usages;
208 x509_certificate_model::GetUsageStrings(cert_hnd, &usages);
209 cert_info.SetString("general.usages", base::JoinString(usages, "\n"));
210
211 // Standard certificate details.
212 const std::string alternative_text =
213 l10n_util::GetStringUTF8(IDS_CERT_INFO_FIELD_NOT_PRESENT);
214 cert_info.SetString(
215 "general.title",
216 l10n_util::GetStringFUTF8(
217 IDS_CERT_INFO_DIALOG_TITLE,
218 base::UTF8ToUTF16(x509_certificate_model::GetTitle(cert_hnd))));
219
220 // Issued to information.
221 cert_info.SetString("general.issued-cn",
222 x509_certificate_model::GetSubjectCommonName(cert_hnd, alternative_text));
223 cert_info.SetString("general.issued-o",
224 x509_certificate_model::GetSubjectOrgName(cert_hnd, alternative_text));
225 cert_info.SetString("general.issued-ou",
226 x509_certificate_model::GetSubjectOrgUnitName(cert_hnd,
227 alternative_text));
228
229 // Issuer information.
230 cert_info.SetString("general.issuer-cn",
231 x509_certificate_model::GetIssuerCommonName(cert_hnd, alternative_text));
232 cert_info.SetString("general.issuer-o",
233 x509_certificate_model::GetIssuerOrgName(cert_hnd, alternative_text));
234 cert_info.SetString("general.issuer-ou",
235 x509_certificate_model::GetIssuerOrgUnitName(cert_hnd, alternative_text));
236
237 // Validity period.
238 base::Time issued, expires;
239 std::string issued_str, expires_str;
240 if (x509_certificate_model::GetTimes(cert_hnd, &issued, &expires)) {
241 issued_str = base::UTF16ToUTF8(
242 base::TimeFormatFriendlyDateAndTime(issued));
243 expires_str = base::UTF16ToUTF8(
244 base::TimeFormatFriendlyDateAndTime(expires));
245 } else {
246 issued_str = alternative_text;
247 expires_str = alternative_text;
248 }
249 cert_info.SetString("general.issue-date", issued_str);
250 cert_info.SetString("general.expiry-date", expires_str);
251
252 cert_info.SetString("general.sha256",
253 x509_certificate_model::HashCertSHA256(cert_hnd));
254 cert_info.SetString("general.sha1",
255 x509_certificate_model::HashCertSHA1(cert_hnd));
256
257 // Certificate hierarchy is constructed from bottom up.
258 base::Value children;
259 int index = 0;
260 for (const auto& cert : nss_certs_) {
261 base::Value cert_node(base::Value::Type::DICTIONARY);
262 cert_node.SetKey("label",
263 base::Value(x509_certificate_model::GetTitle(cert.get())));
264 cert_node.SetPath({"payload", "index"}, base::Value(index));
265 // Add the child from the previous iteration.
266 if (!children.is_none())
267 cert_node.SetKey("children", std::move(children));
268
269 // Add this node to the children list for the next iteration.
270 children = base::Value(base::Value::Type::LIST);
271 children.Append(std::move(cert_node));
272 ++index;
273 }
274 // Set the last node as the top of the certificate hierarchy.
275 cert_info.SetKey("hierarchy", std::move(children));
276
277 base::JSONWriter::Write(cert_info, &data);
278
279 return data;
280 }
281
OnDialogShown(content::WebUI * webui)282 void CertificateViewerDialog::OnDialogShown(content::WebUI* webui) {
283 webui_ = webui;
284 }
285
OnDialogClosed(const std::string & json_retval)286 void CertificateViewerDialog::OnDialogClosed(const std::string& json_retval) {
287 // Don't |delete this|: owned by the constrained dialog manager.
288 }
289
OnCloseContents(WebContents * source,bool * out_close_dialog)290 void CertificateViewerDialog::OnCloseContents(WebContents* source,
291 bool* out_close_dialog) {
292 *out_close_dialog = true;
293 }
294
ShouldShowDialogTitle() const295 bool CertificateViewerDialog::ShouldShowDialogTitle() const {
296 return true;
297 }
298
299 ////////////////////////////////////////////////////////////////////////////////
300 // CertificateViewerDialogHandler
301
CertificateViewerDialogHandler(CertificateViewerDialog * dialog,net::ScopedCERTCertificateList cert_chain)302 CertificateViewerDialogHandler::CertificateViewerDialogHandler(
303 CertificateViewerDialog* dialog,
304 net::ScopedCERTCertificateList cert_chain)
305 : dialog_(dialog), cert_chain_(std::move(cert_chain)) {}
306
~CertificateViewerDialogHandler()307 CertificateViewerDialogHandler::~CertificateViewerDialogHandler() {
308 }
309
RegisterMessages()310 void CertificateViewerDialogHandler::RegisterMessages() {
311 web_ui()->RegisterMessageCallback(
312 "exportCertificate",
313 base::BindRepeating(&CertificateViewerDialogHandler::ExportCertificate,
314 base::Unretained(this)));
315 web_ui()->RegisterMessageCallback(
316 "requestCertificateFields",
317 base::BindRepeating(
318 &CertificateViewerDialogHandler::RequestCertificateFields,
319 base::Unretained(this)));
320 }
321
ExportCertificate(const base::ListValue * args)322 void CertificateViewerDialogHandler::ExportCertificate(
323 const base::ListValue* args) {
324 int cert_index = GetCertificateIndex(args);
325 if (cert_index < 0)
326 return;
327
328 gfx::NativeWindow window =
329 platform_util::GetTopLevel(dialog_->GetNativeWebContentsModalDialog());
330 ShowCertExportDialog(web_ui()->GetWebContents(),
331 window,
332 cert_chain_.begin() + cert_index,
333 cert_chain_.end());
334 }
335
RequestCertificateFields(const base::ListValue * args)336 void CertificateViewerDialogHandler::RequestCertificateFields(
337 const base::ListValue* args) {
338 int cert_index = GetCertificateIndex(args);
339 if (cert_index < 0)
340 return;
341
342 CERTCertificate* cert = cert_chain_[cert_index].get();
343
344 CertNodeBuilder version_node(IDS_CERT_DETAILS_VERSION);
345 std::string version = x509_certificate_model::GetVersion(cert);
346 if (!version.empty()) {
347 version_node.Payload(l10n_util::GetStringFUTF8(
348 IDS_CERT_DETAILS_VERSION_FORMAT, base::UTF8ToUTF16(version)));
349 }
350
351 CertNodeBuilder issued_node_builder(IDS_CERT_DETAILS_NOT_BEFORE);
352 CertNodeBuilder expires_node_builder(IDS_CERT_DETAILS_NOT_AFTER);
353 base::Time issued, expires;
354 if (x509_certificate_model::GetTimes(cert, &issued, &expires)) {
355 issued_node_builder.Payload(base::UTF16ToUTF8(
356 base::TimeFormatShortDateAndTimeWithTimeZone(issued)));
357 expires_node_builder.Payload(base::UTF16ToUTF8(
358 base::TimeFormatShortDateAndTimeWithTimeZone(expires)));
359 }
360
361 x509_certificate_model::Extensions extensions;
362 x509_certificate_model::GetExtensions(
363 l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_CRITICAL),
364 l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_NON_CRITICAL),
365 cert, &extensions);
366
367 std::unique_ptr<base::DictionaryValue> details_extensions;
368 if (!extensions.empty()) {
369 CertNodeBuilder details_extensions_builder(IDS_CERT_DETAILS_EXTENSIONS);
370 for (const x509_certificate_model::Extension& extension : extensions) {
371 details_extensions_builder.Child(
372 CertNodeBuilder(extension.name).Payload(extension.value).Build());
373 }
374 details_extensions = details_extensions_builder.Build();
375 }
376
377 base::ListValue root_list;
378 root_list.Append(
379 CertNodeBuilder(x509_certificate_model::GetTitle(cert))
380 .Child(
381 CertNodeBuilder(
382 l10n_util::GetStringUTF8(IDS_CERT_DETAILS_CERTIFICATE))
383 // Main certificate fields.
384 .Child(version_node.Build())
385 .Child(
386 CertNodeBuilder(IDS_CERT_DETAILS_SERIAL_NUMBER)
387 .Payload(
388 x509_certificate_model::GetSerialNumberHexified(
389 cert, l10n_util::GetStringUTF8(
390 IDS_CERT_INFO_FIELD_NOT_PRESENT)))
391 .Build())
392 .Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_ALG)
393 .Payload(x509_certificate_model::
394 ProcessSecAlgorithmSignature(cert))
395 .Build())
396 .Child(
397 CertNodeBuilder(IDS_CERT_DETAILS_ISSUER)
398 .Payload(x509_certificate_model::GetIssuerName(cert))
399 .Build())
400 // Validity period.
401 .Child(CertNodeBuilder(IDS_CERT_DETAILS_VALIDITY)
402 .Child(issued_node_builder.Build())
403 .Child(expires_node_builder.Build())
404 .Build())
405 .Child(
406 CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT)
407 .Payload(x509_certificate_model::GetSubjectName(cert))
408 .Build())
409 // Subject key information.
410 .Child(
411 CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY_INFO)
412 .Child(
413 CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY_ALG)
414 .Payload(
415 x509_certificate_model::
416 ProcessSecAlgorithmSubjectPublicKey(
417 cert))
418 .Build())
419 .Child(CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY)
420 .Payload(
421 x509_certificate_model::
422 ProcessSubjectPublicKeyInfo(cert))
423 .Build())
424 .Build())
425 // Extensions.
426 .ChildIfNotNull(std::move(details_extensions))
427 .Child(
428 CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_ALG)
429 .Payload(x509_certificate_model::
430 ProcessSecAlgorithmSignatureWrap(cert))
431 .Build())
432 .Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_VALUE)
433 .Payload(x509_certificate_model::
434 ProcessRawBitsSignatureWrap(cert))
435 .Build())
436 .Child(
437 CertNodeBuilder(IDS_CERT_INFO_FINGERPRINTS_GROUP)
438 .Child(CertNodeBuilder(
439 IDS_CERT_INFO_SHA256_FINGERPRINT_LABEL)
440 .Payload(
441 x509_certificate_model::HashCertSHA256(
442 cert))
443 .Build())
444 .Child(
445 CertNodeBuilder(
446 IDS_CERT_INFO_SHA1_FINGERPRINT_LABEL)
447 .Payload(x509_certificate_model::HashCertSHA1(
448 cert))
449 .Build())
450 .Build())
451 .Build())
452 .Build());
453
454 // Send certificate information to javascript.
455 web_ui()->CallJavascriptFunctionUnsafe("cert_viewer.getCertificateFields",
456 root_list);
457 }
458
GetCertificateIndex(const base::ListValue * args) const459 int CertificateViewerDialogHandler::GetCertificateIndex(
460 const base::ListValue* args) const {
461 int cert_index;
462 double val;
463 if (!(args->GetDouble(0, &val)))
464 return -1;
465 cert_index = static_cast<int>(val);
466 if (cert_index < 0 || cert_index >= static_cast<int>(cert_chain_.size()))
467 return -1;
468 return cert_index;
469 }
470