blob: 450987872202f43fa3fb0927ba6d3de5bbf1561e [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/certificate_viewer_webui.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/certificate_viewer.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/ui/certificate_dialogs.h"
#include "chrome/browser/ui/webui/certificate_viewer_ui.h"
#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
#include "chrome/common/net/x509_certificate_model.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/web_contents.h"
#include "net/cert/x509_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(USE_NSS_CERTS)
#include "chrome/common/net/x509_certificate_model_nss.h"
#include "net/cert/x509_util_nss.h"
#endif
using content::WebContents;
using content::WebUIMessageHandler;
namespace {
// Helper class for building a Value representation of a certificate. The class
// gathers data for a single node of the representation tree and builds a
// `base::Value::Dict` out of that.
class CertNodeBuilder {
public:
// Starts the node with "label" set to |label|.
explicit CertNodeBuilder(base::StringPiece label);
// Convenience version: Converts |label_id| to the corresponding resource
// string, then delegates to the other constructor.
explicit CertNodeBuilder(int label_id);
CertNodeBuilder(const CertNodeBuilder&) = delete;
CertNodeBuilder& operator=(const CertNodeBuilder&) = delete;
// Builder methods all return |*this| so that they can be chained in single
// expressions.
// Sets the "payload.val" field. Call this at most once.
CertNodeBuilder& Payload(base::StringPiece payload);
// Adds |child| in the list keyed "children". Can be called multiple times.
CertNodeBuilder& Child(base::Value::Dict child);
// Similar to Child, but if the argument is null, then this does not add
// anything.
CertNodeBuilder& ChildIfNotNullopt(absl::optional<base::Value::Dict> child);
// Creates a base::Value::Dict representation of the collected information.
// Only call this once.
base::Value::Dict Build();
private:
base::Value::Dict node_;
base::Value::List children_;
// |built_| is false until Build() is called. Once it is |true|, |node_| and
// |children_| are no longer valid for use.
bool built_ = false;
};
CertNodeBuilder::CertNodeBuilder(base::StringPiece label) {
node_.Set("label", label);
}
CertNodeBuilder::CertNodeBuilder(int label_id)
: CertNodeBuilder(l10n_util::GetStringUTF8(label_id)) {}
CertNodeBuilder& CertNodeBuilder::Payload(base::StringPiece payload) {
DCHECK(!node_.FindByDottedPath("payload.val"));
node_.SetByDottedPath("payload.val", payload);
return *this;
}
CertNodeBuilder& CertNodeBuilder::Child(base::Value::Dict child) {
children_.Append(std::move(child));
return *this;
}
CertNodeBuilder& CertNodeBuilder::ChildIfNotNullopt(
absl::optional<base::Value::Dict> child) {
if (child)
return Child(std::move(*child));
return *this;
}
base::Value::Dict CertNodeBuilder::Build() {
DCHECK(!built_);
if (!children_.empty()) {
node_.Set("children", std::move(children_));
}
built_ = true;
return std::move(node_);
}
} // namespace
// Shows a certificate using the WebUI certificate viewer.
void ShowCertificateViewer(WebContents* web_contents,
gfx::NativeWindow parent,
net::X509Certificate* cert) {
std::vector<std::string> nicknames;
#if BUILDFLAG(USE_NSS_CERTS)
net::ScopedCERTCertificateList nss_certs =
net::x509_util::CreateCERTCertificateListFromX509Certificate(cert);
// If any of the certs could not be parsed by NSS, |nss_certs| will be an
// empty list and |nicknames| will not be populated, which is fine as a
// fallback.
for (const auto& nss_cert : nss_certs) {
nicknames.push_back(x509_certificate_model::GetRawNickname(nss_cert.get()));
}
#endif
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> cert_buffers;
cert_buffers.push_back(bssl::UpRef(cert->cert_buffer()));
for (const auto& intermediate : cert->intermediate_buffers()) {
cert_buffers.push_back(bssl::UpRef(intermediate));
}
CertificateViewerDialog::ShowConstrained(
std::move(cert_buffers), std::move(nicknames), web_contents, parent);
}
#if !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC))
void ShowCertificateViewerForClientAuth(content::WebContents* web_contents,
gfx::NativeWindow parent,
net::X509Certificate* cert) {
ShowCertificateViewer(web_contents, parent, cert);
}
#endif
////////////////////////////////////////////////////////////////////////////////
// CertificateViewerDialog
#if BUILDFLAG(USE_NSS_CERTS)
// static
CertificateViewerDialog* CertificateViewerDialog::ShowConstrained(
net::ScopedCERTCertificateList nss_certs,
WebContents* web_contents,
gfx::NativeWindow parent) {
std::vector<std::string> nicknames;
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> cert_buffers;
for (const auto& cert : nss_certs) {
nicknames.push_back(x509_certificate_model::GetRawNickname(cert.get()));
cert_buffers.push_back(net::x509_util::CreateCryptoBuffer(
base::make_span(cert->derCert.data, cert->derCert.len)));
}
return ShowConstrained(std::move(cert_buffers), std::move(nicknames),
web_contents, parent);
}
#endif
// static
CertificateViewerDialog* CertificateViewerDialog::ShowConstrained(
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> certs,
std::vector<std::string> cert_nicknames,
content::WebContents* web_contents,
gfx::NativeWindow parent) {
CertificateViewerDialog* dialog_ptr =
new CertificateViewerDialog(std::move(certs), std::move(cert_nicknames));
auto dialog = base::WrapUnique(dialog_ptr);
// TODO(bshe): UI tweaks needed for Aura HTML Dialog, such as adding padding
// on the title for Aura ConstrainedWebDialogUI.
dialog_ptr->delegate_ = ShowConstrainedWebDialog(
web_contents->GetBrowserContext(), std::move(dialog), web_contents);
// Clear the zoom level for the dialog so that it is not affected by the page
// zoom setting.
content::WebContents* dialog_web_contents =
dialog_ptr->delegate_->GetWebContents();
const GURL dialog_url = dialog_ptr->GetDialogContentURL();
content::HostZoomMap::Get(dialog_web_contents->GetSiteInstance())
->SetZoomLevelForHostAndScheme(dialog_url.scheme(), dialog_url.host(), 0);
return dialog_ptr; // For tests.
}
gfx::NativeWindow CertificateViewerDialog::GetNativeWebContentsModalDialog() {
return delegate_->GetNativeDialog();
}
CertificateViewerDialog::CertificateViewerDialog(
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> certs,
std::vector<std::string> cert_nicknames) {
for (size_t i = 0; i < certs.size(); ++i) {
std::string nickname;
if (i < cert_nicknames.size())
nickname = std::move(cert_nicknames[i]);
certs_.emplace_back(std::move(certs[i]), std::move(nickname));
}
// Construct the dialog title from the certificate.
title_ = l10n_util::GetStringFUTF16(
IDS_CERT_INFO_DIALOG_TITLE, base::UTF8ToUTF16(certs_.front().GetTitle()));
}
CertificateViewerDialog::~CertificateViewerDialog() = default;
ui::ModalType CertificateViewerDialog::GetDialogModalType() const {
return ui::MODAL_TYPE_NONE;
}
std::u16string CertificateViewerDialog::GetDialogTitle() const {
return title_;
}
GURL CertificateViewerDialog::GetDialogContentURL() const {
return GURL(chrome::kChromeUICertificateViewerURL);
}
void CertificateViewerDialog::GetWebUIMessageHandlers(
std::vector<WebUIMessageHandler*>* handlers) const {
handlers->push_back(new CertificateViewerDialogHandler(
const_cast<CertificateViewerDialog*>(this), &certs_));
}
void CertificateViewerDialog::GetDialogSize(gfx::Size* size) const {
const int kDefaultWidth = 544;
const int kDefaultHeight = 628;
size->SetSize(kDefaultWidth, kDefaultHeight);
}
std::string HandleOptionalOrError(
const x509_certificate_model::OptionalStringOrError& s) {
if (absl::holds_alternative<x509_certificate_model::Error>(s))
return l10n_util::GetStringUTF8(IDS_CERT_DUMP_ERROR);
else if (absl::holds_alternative<x509_certificate_model::NotPresent>(s))
return l10n_util::GetStringUTF8(IDS_CERT_INFO_FIELD_NOT_PRESENT);
return absl::get<std::string>(s);
}
std::string CertificateViewerDialog::GetDialogArgs() const {
std::string data;
// Certificate information. The keys in this dictionary's general key
// correspond to the IDs in the Html page.
base::Value::Dict cert_info;
const x509_certificate_model::X509CertificateModel& model = certs_.front();
cert_info.Set("isError", !model.is_valid());
cert_info.SetByDottedPath(
"general.title",
l10n_util::GetStringFUTF8(IDS_CERT_INFO_DIALOG_TITLE,
base::UTF8ToUTF16(model.GetTitle())));
if (model.is_valid()) {
// Standard certificate details.
const std::string alternative_text =
l10n_util::GetStringUTF8(IDS_CERT_INFO_FIELD_NOT_PRESENT);
// Issued to information.
cert_info.SetByDottedPath(
"general.issued-cn",
HandleOptionalOrError(model.GetSubjectCommonName()));
cert_info.SetByDottedPath("general.issued-o",
HandleOptionalOrError(model.GetSubjectOrgName()));
cert_info.SetByDottedPath(
"general.issued-ou",
HandleOptionalOrError(model.GetSubjectOrgUnitName()));
// Issuer information.
cert_info.SetByDottedPath(
"general.issuer-cn",
HandleOptionalOrError(model.GetIssuerCommonName()));
cert_info.SetByDottedPath("general.issuer-o",
HandleOptionalOrError(model.GetIssuerOrgName()));
cert_info.SetByDottedPath(
"general.issuer-ou",
HandleOptionalOrError(model.GetIssuerOrgUnitName()));
// Validity period.
base::Time issued, expires;
std::string issued_str, expires_str;
if (model.GetTimes(&issued, &expires)) {
issued_str =
base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(issued));
expires_str =
base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(expires));
} else {
issued_str = alternative_text;
expires_str = alternative_text;
}
cert_info.SetByDottedPath("general.issue-date", issued_str);
cert_info.SetByDottedPath("general.expiry-date", expires_str);
}
cert_info.SetByDottedPath("general.sha256",
model.HashCertSHA256WithSeparators());
cert_info.SetByDottedPath("general.sha1", model.HashCertSHA1WithSeparators());
// Certificate hierarchy is constructed from bottom up.
base::Value::List children;
int index = 0;
for (const auto& cert : certs_) {
base::Value::Dict cert_node;
cert_node.Set("label", base::Value(cert.GetTitle()));
cert_node.SetByDottedPath("payload.index", base::Value(index));
// Add the child from the previous iteration.
if (!children.empty())
cert_node.Set("children", std::move(children));
// Add this node to the children list for the next iteration.
children = base::Value::List();
children.Append(std::move(cert_node));
++index;
}
// Set the last node as the top of the certificate hierarchy.
cert_info.Set("hierarchy", std::move(children));
base::JSONWriter::Write(cert_info, &data);
return data;
}
void CertificateViewerDialog::OnDialogShown(content::WebUI* webui) {
webui_ = webui;
}
void CertificateViewerDialog::OnDialogClosed(const std::string& json_retval) {
// Don't |delete this|: owned by the constrained dialog manager.
}
void CertificateViewerDialog::OnCloseContents(WebContents* source,
bool* out_close_dialog) {
*out_close_dialog = true;
}
bool CertificateViewerDialog::ShouldShowDialogTitle() const {
return true;
}
////////////////////////////////////////////////////////////////////////////////
// CertificateViewerDialogHandler
CertificateViewerDialogHandler::CertificateViewerDialogHandler(
CertificateViewerDialog* dialog,
const std::vector<x509_certificate_model::X509CertificateModel>* certs)
: dialog_(dialog), certs_(certs) {}
CertificateViewerDialogHandler::~CertificateViewerDialogHandler() {
}
void CertificateViewerDialogHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"exportCertificate",
base::BindRepeating(
&CertificateViewerDialogHandler::HandleExportCertificate,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"requestCertificateFields",
base::BindRepeating(
&CertificateViewerDialogHandler::HandleRequestCertificateFields,
base::Unretained(this)));
}
void CertificateViewerDialogHandler::HandleExportCertificate(
const base::Value::List& args) {
int cert_index = GetCertificateIndex(args[0].GetInt());
if (cert_index < 0)
return;
gfx::NativeWindow window =
platform_util::GetTopLevel(platform_util::GetViewForWindow(
dialog_->GetNativeWebContentsModalDialog()));
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> export_certs;
for (const auto& cert : base::make_span(*certs_).subspan(cert_index)) {
export_certs.push_back(bssl::UpRef(cert.cert_buffer()));
}
ShowCertExportDialog(web_ui()->GetWebContents(), window,
std::move(export_certs),
(*certs_)[cert_index].GetTitle());
}
void CertificateViewerDialogHandler::HandleRequestCertificateFields(
const base::Value::List& args) {
AllowJavascript();
const base::Value& callback_id = args[0];
int cert_index = GetCertificateIndex(args[1].GetInt());
if (cert_index < 0)
return;
const x509_certificate_model::X509CertificateModel& model =
(*certs_)[cert_index];
CertNodeBuilder contents_builder(IDS_CERT_DETAILS_CERTIFICATE);
if (model.is_valid()) {
CertNodeBuilder issued_node_builder(IDS_CERT_DETAILS_NOT_BEFORE);
CertNodeBuilder expires_node_builder(IDS_CERT_DETAILS_NOT_AFTER);
base::Time issued, expires;
if (model.GetTimes(&issued, &expires)) {
issued_node_builder.Payload(base::UTF16ToUTF8(
base::TimeFormatShortDateAndTimeWithTimeZone(issued)));
expires_node_builder.Payload(base::UTF16ToUTF8(
base::TimeFormatShortDateAndTimeWithTimeZone(expires)));
}
std::vector<x509_certificate_model::Extension> extensions =
model.GetExtensions(
l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_CRITICAL),
l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_NON_CRITICAL));
absl::optional<base::Value::Dict> details_extensions;
if (!extensions.empty()) {
CertNodeBuilder details_extensions_builder(IDS_CERT_DETAILS_EXTENSIONS);
for (const x509_certificate_model::Extension& extension : extensions) {
details_extensions_builder.Child(
CertNodeBuilder(extension.name).Payload(extension.value).Build());
}
details_extensions = details_extensions_builder.Build();
}
contents_builder
// Main certificate fields.
.Child(CertNodeBuilder(IDS_CERT_DETAILS_VERSION)
.Payload(l10n_util::GetStringFUTF8(
IDS_CERT_DETAILS_VERSION_FORMAT,
base::UTF8ToUTF16(model.GetVersion())))
.Build())
.Child(CertNodeBuilder(IDS_CERT_DETAILS_SERIAL_NUMBER)
.Payload(model.GetSerialNumberHexified())
.Build())
.Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_ALG)
.Payload(model.ProcessSecAlgorithmSignature())
.Build())
.Child(CertNodeBuilder(IDS_CERT_DETAILS_ISSUER)
.Payload(HandleOptionalOrError(model.GetIssuerName()))
.Build())
// Validity period.
.Child(CertNodeBuilder(IDS_CERT_DETAILS_VALIDITY)
.Child(issued_node_builder.Build())
.Child(expires_node_builder.Build())
.Build())
.Child(CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT)
.Payload(HandleOptionalOrError(model.GetSubjectName()))
.Build())
// Subject key information.
.Child(
CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY_INFO)
.Child(CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY_ALG)
.Payload(model.ProcessSecAlgorithmSubjectPublicKey())
.Build())
.Child(CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY)
.Payload(model.ProcessSubjectPublicKeyInfo())
.Build())
.Build())
// Extensions.
.ChildIfNotNullopt(std::move(details_extensions))
.Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_ALG)
.Payload(model.ProcessSecAlgorithmSignatureWrap())
.Build())
.Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_VALUE)
.Payload(model.ProcessRawBitsSignatureWrap())
.Build());
}
contents_builder.Child(
CertNodeBuilder(IDS_CERT_INFO_FINGERPRINTS_GROUP)
.Child(CertNodeBuilder(IDS_CERT_INFO_SHA256_FINGERPRINT_LABEL)
.Payload(model.HashCertSHA256WithSeparators())
.Build())
.Child(CertNodeBuilder(IDS_CERT_INFO_SHA1_FINGERPRINT_LABEL)
.Payload(model.HashCertSHA1WithSeparators())
.Build())
.Build());
base::Value::List root_list;
root_list.Append(CertNodeBuilder(model.GetTitle())
.Child(contents_builder.Build())
.Build());
// Send certificate information to javascript.
ResolveJavascriptCallback(callback_id, root_list);
}
int CertificateViewerDialogHandler::GetCertificateIndex(
int requested_index) const {
int cert_index = requested_index;
if (cert_index < 0 || static_cast<size_t>(cert_index) >= certs_->size())
return -1;
return cert_index;
}