| // 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/webui/certificate_viewer_webui.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/json/json_writer.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/certificate_viewer.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/ui/browser_dialogs.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 "content/public/browser/web_contents.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| 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 |
| // DictionaryValue 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); |
| |
| // 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(std::unique_ptr<base::DictionaryValue> child); |
| |
| // Similar to Child, but if the argument is null, then this does not add |
| // anything. |
| CertNodeBuilder& ChildIfNotNull(std::unique_ptr<base::DictionaryValue> child); |
| |
| // Creates a DictionaryValue representation of the collected information. Only |
| // call this once. |
| std::unique_ptr<base::DictionaryValue> Build(); |
| |
| private: |
| base::DictionaryValue node_; |
| base::ListValue children_; |
| // |built_| is false until Build() is called. Once it is |true|, |node_| and |
| // |children_| are no longer valid for use. |
| bool built_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(CertNodeBuilder); |
| }; |
| |
| CertNodeBuilder::CertNodeBuilder(base::StringPiece label) { |
| node_.SetString("label", label); |
| } |
| |
| CertNodeBuilder::CertNodeBuilder(int label_id) |
| : CertNodeBuilder(l10n_util::GetStringUTF8(label_id)) {} |
| |
| CertNodeBuilder& CertNodeBuilder::Payload(base::StringPiece payload) { |
| DCHECK(!node_.HasKey("payload.val")); |
| node_.SetString("payload.val", payload); |
| return *this; |
| } |
| |
| CertNodeBuilder& CertNodeBuilder::Child( |
| std::unique_ptr<base::DictionaryValue> child) { |
| children_.Append(std::move(child)); |
| return *this; |
| } |
| |
| CertNodeBuilder& CertNodeBuilder::ChildIfNotNull( |
| std::unique_ptr<base::DictionaryValue> child) { |
| if (child) |
| return Child(std::move(child)); |
| return *this; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> CertNodeBuilder::Build() { |
| DCHECK(!built_); |
| if (!children_.empty()) { |
| node_.Set("children", base::MakeUnique<base::Value>(std::move(children_))); |
| } |
| built_ = true; |
| return base::MakeUnique<base::DictionaryValue>(std::move(node_)); |
| } |
| |
| } // namespace |
| |
| // Shows a certificate using the WebUI certificate viewer. |
| void ShowCertificateViewer(WebContents* web_contents, |
| gfx::NativeWindow parent, |
| net::X509Certificate* cert) { |
| CertificateViewerDialog* dialog = new CertificateViewerDialog(cert); |
| dialog->Show(web_contents, parent); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // CertificateViewerDialog |
| |
| CertificateViewerModalDialog::CertificateViewerModalDialog( |
| net::X509Certificate* cert) |
| : cert_(cert), webui_(NULL), window_(NULL) { |
| // Construct the dialog title from the certificate. |
| title_ = l10n_util::GetStringFUTF16( |
| IDS_CERT_INFO_DIALOG_TITLE, |
| base::UTF8ToUTF16( |
| x509_certificate_model::GetTitle(cert_->os_cert_handle()))); |
| } |
| |
| CertificateViewerModalDialog::~CertificateViewerModalDialog() { |
| } |
| |
| void CertificateViewerModalDialog::Show(content::WebContents* web_contents, |
| gfx::NativeWindow parent) { |
| window_ = chrome::ShowWebDialog(parent, |
| web_contents->GetBrowserContext(), |
| this); |
| } |
| |
| gfx::NativeWindow |
| CertificateViewerModalDialog::GetNativeWebContentsModalDialog() { |
| #if defined(USE_AURA) |
| return window_; |
| #else |
| NOTREACHED(); |
| return NULL; |
| #endif |
| } |
| |
| ui::ModalType CertificateViewerModalDialog::GetDialogModalType() const { |
| return ui::MODAL_TYPE_SYSTEM; |
| } |
| |
| base::string16 CertificateViewerModalDialog::GetDialogTitle() const { |
| return title_; |
| } |
| |
| GURL CertificateViewerModalDialog::GetDialogContentURL() const { |
| return GURL(chrome::kChromeUICertificateViewerDialogURL); |
| } |
| |
| void CertificateViewerModalDialog::GetWebUIMessageHandlers( |
| std::vector<WebUIMessageHandler*>* handlers) const { |
| handlers->push_back(new CertificateViewerDialogHandler( |
| const_cast<CertificateViewerModalDialog*>(this), cert_.get())); |
| } |
| |
| void CertificateViewerModalDialog::GetDialogSize(gfx::Size* size) const { |
| const int kDefaultWidth = 544; |
| const int kDefaultHeight = 628; |
| size->SetSize(kDefaultWidth, kDefaultHeight); |
| } |
| |
| std::string CertificateViewerModalDialog::GetDialogArgs() const { |
| std::string data; |
| |
| // Certificate information. The keys in this dictionary's general key |
| // correspond to the IDs in the Html page. |
| base::DictionaryValue cert_info; |
| net::X509Certificate::OSCertHandle cert_hnd = cert_->os_cert_handle(); |
| |
| // Get the certificate chain. |
| 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()); |
| |
| // Certificate usage. |
| std::vector<std::string> usages; |
| x509_certificate_model::GetUsageStrings(cert_hnd, &usages); |
| std::string usagestr; |
| for (std::vector<std::string>::iterator it = usages.begin(); |
| it != usages.end(); ++it) { |
| if (usagestr.length() > 0) { |
| usagestr += "\n"; |
| } |
| usagestr += *it; |
| } |
| cert_info.SetString("general.usages", usagestr); |
| |
| // Standard certificate details. |
| const std::string alternative_text = |
| l10n_util::GetStringUTF8(IDS_CERT_INFO_FIELD_NOT_PRESENT); |
| cert_info.SetString("general.title", l10n_util::GetStringFUTF8( |
| IDS_CERT_INFO_DIALOG_TITLE, |
| base::UTF8ToUTF16(x509_certificate_model::GetTitle( |
| cert_chain.front())))); |
| |
| // Issued to information. |
| cert_info.SetString("general.issued-cn", |
| x509_certificate_model::GetSubjectCommonName(cert_hnd, alternative_text)); |
| cert_info.SetString("general.issued-o", |
| x509_certificate_model::GetSubjectOrgName(cert_hnd, alternative_text)); |
| cert_info.SetString("general.issued-ou", |
| x509_certificate_model::GetSubjectOrgUnitName(cert_hnd, |
| alternative_text)); |
| |
| // Issuer information. |
| cert_info.SetString("general.issuer-cn", |
| x509_certificate_model::GetIssuerCommonName(cert_hnd, alternative_text)); |
| cert_info.SetString("general.issuer-o", |
| x509_certificate_model::GetIssuerOrgName(cert_hnd, alternative_text)); |
| cert_info.SetString("general.issuer-ou", |
| x509_certificate_model::GetIssuerOrgUnitName(cert_hnd, alternative_text)); |
| |
| // Validity period. |
| base::Time issued, expires; |
| std::string issued_str, expires_str; |
| if (x509_certificate_model::GetTimes(cert_hnd, &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.SetString("general.issue-date", issued_str); |
| cert_info.SetString("general.expiry-date", expires_str); |
| |
| cert_info.SetString("general.sha256", |
| x509_certificate_model::HashCertSHA256(cert_hnd)); |
| cert_info.SetString("general.sha1", |
| x509_certificate_model::HashCertSHA1(cert_hnd)); |
| |
| // Certificate hierarchy is constructed from bottom up. |
| std::unique_ptr<base::ListValue> children; |
| int index = 0; |
| for (net::X509Certificate::OSCertHandles::const_iterator i = |
| cert_chain.begin(); i != cert_chain.end(); ++i, ++index) { |
| std::unique_ptr<base::DictionaryValue> cert_node( |
| new base::DictionaryValue()); |
| base::ListValue cert_details; |
| cert_node->SetString("label", x509_certificate_model::GetTitle(*i).c_str()); |
| cert_node->SetDouble("payload.index", index); |
| // Add the child from the previous iteration. |
| if (children) |
| cert_node->Set("children", std::move(children)); |
| |
| // Add this node to the children list for the next iteration. |
| children = base::MakeUnique<base::ListValue>(); |
| children->Append(std::move(cert_node)); |
| } |
| // 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 CertificateViewerModalDialog::OnDialogShown( |
| content::WebUI* webui, |
| content::RenderViewHost* render_view_host) { |
| webui_ = webui; |
| } |
| |
| void CertificateViewerModalDialog::OnDialogClosed( |
| const std::string& json_retval) { |
| } |
| |
| void CertificateViewerModalDialog::OnCloseContents(WebContents* source, |
| bool* out_close_dialog) { |
| *out_close_dialog = true; |
| } |
| |
| bool CertificateViewerModalDialog::ShouldShowDialogTitle() const { |
| return true; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // CertificateViewerDialog |
| |
| CertificateViewerDialog::CertificateViewerDialog(net::X509Certificate* cert) |
| : CertificateViewerModalDialog(cert), |
| dialog_(NULL) { |
| } |
| |
| CertificateViewerDialog::~CertificateViewerDialog() { |
| } |
| |
| void CertificateViewerDialog::Show(WebContents* web_contents, |
| gfx::NativeWindow parent) { |
| // TODO(bshe): UI tweaks needed for Aura HTML Dialog, such as adding padding |
| // on the title for Aura ConstrainedWebDialogUI. |
| dialog_ = ShowConstrainedWebDialog(web_contents->GetBrowserContext(), this, |
| web_contents); |
| } |
| |
| gfx::NativeWindow CertificateViewerDialog::GetNativeWebContentsModalDialog() { |
| return dialog_->GetNativeDialog(); |
| } |
| |
| GURL CertificateViewerDialog::GetDialogContentURL() const { |
| return GURL(chrome::kChromeUICertificateViewerURL); |
| } |
| |
| ui::ModalType CertificateViewerDialog::GetDialogModalType() const { |
| return ui::MODAL_TYPE_NONE; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // CertificateViewerDialogHandler |
| |
| CertificateViewerDialogHandler::CertificateViewerDialogHandler( |
| CertificateViewerModalDialog* dialog, |
| net::X509Certificate* cert) |
| : cert_(cert), dialog_(dialog) { |
| 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()); |
| } |
| |
| CertificateViewerDialogHandler::~CertificateViewerDialogHandler() { |
| } |
| |
| void CertificateViewerDialogHandler::RegisterMessages() { |
| web_ui()->RegisterMessageCallback("exportCertificate", |
| base::Bind(&CertificateViewerDialogHandler::ExportCertificate, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback("requestCertificateFields", |
| base::Bind(&CertificateViewerDialogHandler::RequestCertificateFields, |
| base::Unretained(this))); |
| } |
| |
| void CertificateViewerDialogHandler::ExportCertificate( |
| const base::ListValue* args) { |
| int cert_index = GetCertificateIndex(args); |
| if (cert_index < 0) |
| return; |
| |
| gfx::NativeWindow window = |
| platform_util::GetTopLevel(dialog_->GetNativeWebContentsModalDialog()); |
| ShowCertExportDialog(web_ui()->GetWebContents(), |
| window, |
| cert_chain_.begin() + cert_index, |
| cert_chain_.end()); |
| } |
| |
| void CertificateViewerDialogHandler::RequestCertificateFields( |
| const base::ListValue* args) { |
| int cert_index = GetCertificateIndex(args); |
| if (cert_index < 0) |
| return; |
| |
| net::X509Certificate::OSCertHandle cert = cert_chain_[cert_index]; |
| |
| CertNodeBuilder version_node(IDS_CERT_DETAILS_VERSION); |
| std::string version = x509_certificate_model::GetVersion(cert); |
| if (!version.empty()) { |
| version_node.Payload(l10n_util::GetStringFUTF8( |
| IDS_CERT_DETAILS_VERSION_FORMAT, base::UTF8ToUTF16(version))); |
| } |
| |
| CertNodeBuilder issued_node_builder(IDS_CERT_DETAILS_NOT_BEFORE); |
| CertNodeBuilder expires_node_builder(IDS_CERT_DETAILS_NOT_AFTER); |
| base::Time issued, expires; |
| if (x509_certificate_model::GetTimes(cert, &issued, &expires)) { |
| issued_node_builder.Payload(base::UTF16ToUTF8( |
| base::TimeFormatShortDateAndTimeWithTimeZone(issued))); |
| expires_node_builder.Payload(base::UTF16ToUTF8( |
| base::TimeFormatShortDateAndTimeWithTimeZone(expires))); |
| } |
| |
| x509_certificate_model::Extensions extensions; |
| x509_certificate_model::GetExtensions( |
| l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_CRITICAL), |
| l10n_util::GetStringUTF8(IDS_CERT_EXTENSION_NON_CRITICAL), |
| cert, &extensions); |
| |
| std::unique_ptr<base::DictionaryValue> 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(); |
| } |
| |
| base::ListValue root_list; |
| root_list.Append( |
| CertNodeBuilder(x509_certificate_model::GetTitle(cert)) |
| .Child( |
| CertNodeBuilder( |
| l10n_util::GetStringUTF8(IDS_CERT_DETAILS_CERTIFICATE)) |
| // Main certificate fields. |
| .Child(version_node.Build()) |
| .Child( |
| CertNodeBuilder(IDS_CERT_DETAILS_SERIAL_NUMBER) |
| .Payload( |
| x509_certificate_model::GetSerialNumberHexified( |
| cert, l10n_util::GetStringUTF8( |
| IDS_CERT_INFO_FIELD_NOT_PRESENT))) |
| .Build()) |
| .Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_ALG) |
| .Payload(x509_certificate_model:: |
| ProcessSecAlgorithmSignature(cert)) |
| .Build()) |
| .Child( |
| CertNodeBuilder(IDS_CERT_DETAILS_ISSUER) |
| .Payload(x509_certificate_model::GetIssuerName(cert)) |
| .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(x509_certificate_model::GetSubjectName(cert)) |
| .Build()) |
| // Subject key information. |
| .Child( |
| CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY_INFO) |
| .Child( |
| CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY_ALG) |
| .Payload( |
| x509_certificate_model:: |
| ProcessSecAlgorithmSubjectPublicKey( |
| cert)) |
| .Build()) |
| .Child(CertNodeBuilder(IDS_CERT_DETAILS_SUBJECT_KEY) |
| .Payload( |
| x509_certificate_model:: |
| ProcessSubjectPublicKeyInfo(cert)) |
| .Build()) |
| .Build()) |
| // Extensions. |
| .ChildIfNotNull(std::move(details_extensions)) |
| .Child( |
| CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_ALG) |
| .Payload(x509_certificate_model:: |
| ProcessSecAlgorithmSignatureWrap(cert)) |
| .Build()) |
| .Child(CertNodeBuilder(IDS_CERT_DETAILS_CERTIFICATE_SIG_VALUE) |
| .Payload(x509_certificate_model:: |
| ProcessRawBitsSignatureWrap(cert)) |
| .Build()) |
| .Child( |
| CertNodeBuilder(IDS_CERT_INFO_FINGERPRINTS_GROUP) |
| .Child(CertNodeBuilder( |
| IDS_CERT_INFO_SHA256_FINGERPRINT_LABEL) |
| .Payload( |
| x509_certificate_model::HashCertSHA256( |
| cert)) |
| .Build()) |
| .Child( |
| CertNodeBuilder( |
| IDS_CERT_INFO_SHA1_FINGERPRINT_LABEL) |
| .Payload(x509_certificate_model::HashCertSHA1( |
| cert)) |
| .Build()) |
| .Build()) |
| .Build()) |
| .Build()); |
| |
| // Send certificate information to javascript. |
| web_ui()->CallJavascriptFunctionUnsafe("cert_viewer.getCertificateFields", |
| root_list); |
| } |
| |
| int CertificateViewerDialogHandler::GetCertificateIndex( |
| const base::ListValue* args) const { |
| int cert_index; |
| double val; |
| if (!(args->GetDouble(0, &val))) |
| return -1; |
| cert_index = static_cast<int>(val); |
| if (cert_index < 0 || cert_index >= static_cast<int>(cert_chain_.size())) |
| return -1; |
| return cert_index; |
| } |