blob: cebdc256278612206b9a0e7eb1b1b70f4e55b12c [file] [log] [blame]
// Copyright 2019 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 <memory>
#include <string>
#include "base/base64.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/ssl/ssl_cipher_suite_names.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
namespace {
const char kIdParam[] = "id";
const char kMethodParam[] = "method";
} // namespace
class DevToolsProtocolTest : public InProcessBrowserTest,
public content::DevToolsAgentHostClient {
public:
DevToolsProtocolTest() : last_sent_id_(0) {}
protected:
typedef base::RepeatingCallback<bool(const base::Value&)> NotificationMatcher;
// InProcessBrowserTest interface
void TearDownOnMainThread() override { Detach(); }
// DevToolsAgentHostClient interface
void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host,
const std::string& message) override {
auto parsed_message = base::JSONReader::Read(message);
auto id = parsed_message->FindIntPath("id");
if (id) {
// TODO: implement handling of results from method calls (when needed).
} else {
std::string* notification = parsed_message->FindStringPath("method");
EXPECT_TRUE(notification);
notifications_.push_back(*notification);
base::Value* params = parsed_message->FindPath("params");
notification_params_.push_back(params ? params->Clone() : base::Value());
if (waiting_for_notification_ == *notification &&
(waiting_for_notification_matcher_.is_null() ||
waiting_for_notification_matcher_.Run(
notification_params_.back()))) {
waiting_for_notification_ = std::string();
waiting_for_notification_matcher_ = NotificationMatcher();
waiting_for_notification_params_ = notification_params_.back().Clone();
std::move(run_loop_quit_closure_).Run();
}
}
}
void SendCommand(const std::string& method) {
base::Value command(base::Value::Type::DICTIONARY);
command.SetKey(kIdParam, base::Value(++last_sent_id_));
command.SetKey(kMethodParam, base::Value(method));
std::string json_command;
base::JSONWriter::Write(command, &json_command);
agent_host_->DispatchProtocolMessage(this, json_command);
}
void RunLoopUpdatingQuitClosure() {
base::RunLoop run_loop;
CHECK(!run_loop_quit_closure_);
run_loop_quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
void Attach() {
agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents());
agent_host_->AttachClient(this);
}
void Detach() {
if (agent_host_) {
agent_host_->DetachClient(this);
agent_host_ = nullptr;
}
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetWebContentsAt(0);
}
base::Value WaitForNotification(const std::string& notification) {
auto always_match = base::Bind([](const base::Value&) { return true; });
return WaitForMatchingNotification(notification, always_match);
}
base::Value WaitForMatchingNotification(const std::string& notification,
const NotificationMatcher& matcher) {
for (size_t i = 0; i < notifications_.size(); ++i) {
if (notifications_[i] == notification &&
matcher.Run(notification_params_[i])) {
base::Value result = std::move(notification_params_[i]);
notifications_.erase(notifications_.begin() + i);
notification_params_.erase(notification_params_.begin() + i);
return result;
}
}
waiting_for_notification_ = notification;
waiting_for_notification_matcher_ = matcher;
RunLoopUpdatingQuitClosure();
return std::move(waiting_for_notification_params_);
}
private:
// DevToolsAgentHostClient interface
void AgentHostClosed(content::DevToolsAgentHost* agent_host) override {}
scoped_refptr<content::DevToolsAgentHost> agent_host_;
int last_sent_id_;
base::OnceClosure run_loop_quit_closure_;
std::vector<std::string> notifications_;
std::vector<base::Value> notification_params_;
std::string waiting_for_notification_;
NotificationMatcher waiting_for_notification_matcher_;
base::Value waiting_for_notification_params_;
};
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
VisibleSecurityStateChangedNeutralState) {
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
content::WaitForLoadStop(web_contents());
Attach();
SendCommand("Security.enable");
base::Value params =
WaitForNotification("Security.visibleSecurityStateChanged");
std::string* security_state =
params.FindStringPath("visibleSecurityState.securityState");
ASSERT_TRUE(security_state);
ASSERT_EQ(std::string("neutral"), *security_state);
ASSERT_FALSE(
params.FindPath("visibleSecurityState.certificateSecurityState"));
const base::Value* security_state_issue_ids =
params.FindListPath("visibleSecurityState.securityStateIssueIds");
ASSERT_TRUE(std::find(security_state_issue_ids->GetList().begin(),
security_state_issue_ids->GetList().end(),
base::Value("scheme-is-not-cryptographic")) !=
security_state_issue_ids->GetList().end());
}
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, VisibleSecurityStateSecureState) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
browser(), https_server.GetURL("/title1.html"), 1);
content::NavigationEntry* entry =
web_contents()->GetController().GetLastCommittedEntry();
ASSERT_TRUE(entry);
// Extract SSL status data from the navigation entry.
scoped_refptr<net::X509Certificate> page_cert = entry->GetSSL().certificate;
ASSERT_TRUE(page_cert);
int ssl_version =
net::SSLConnectionStatusToVersion(entry->GetSSL().connection_status);
const char* page_protocol;
net::SSLVersionToString(&page_protocol, ssl_version);
const char* page_key_exchange_str;
const char* page_cipher;
const char* page_mac;
bool is_aead;
bool is_tls13;
uint16_t page_cipher_suite =
net::SSLConnectionStatusToCipherSuite(entry->GetSSL().connection_status);
net::SSLCipherSuiteToStrings(&page_key_exchange_str, &page_cipher, &page_mac,
&is_aead, &is_tls13, page_cipher_suite);
std::string page_key_exchange;
if (page_key_exchange_str)
page_key_exchange = page_key_exchange_str;
const char* page_key_exchange_group =
SSL_get_curve_name(entry->GetSSL().key_exchange_group);
std::string page_subject_name;
std::string page_issuer_name;
double page_valid_from = 0.0;
double page_valid_to = 0.0;
if (entry->GetSSL().certificate) {
page_subject_name = entry->GetSSL().certificate->subject().common_name;
page_issuer_name = entry->GetSSL().certificate->issuer().common_name;
page_valid_from = entry->GetSSL().certificate->valid_start().ToDoubleT();
page_valid_to = entry->GetSSL().certificate->valid_expiry().ToDoubleT();
}
bool page_certificate_has_weak_signature =
(entry->GetSSL().cert_status & net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM);
int status = net::ObsoleteSSLStatus(entry->GetSSL().connection_status,
entry->GetSSL().peer_signature_algorithm);
bool page_modern_ssl = status == net::OBSOLETE_SSL_NONE;
bool page_obsolete_ssl_protocol = status & net::OBSOLETE_SSL_MASK_PROTOCOL;
bool page_obsolete_ssl_key_exchange =
status & net::OBSOLETE_SSL_MASK_KEY_EXCHANGE;
bool page_obsolete_ssl_cipher = status & net::OBSOLETE_SSL_MASK_CIPHER;
bool page_obsolete_ssl_signature = status & net::OBSOLETE_SSL_MASK_SIGNATURE;
Attach();
SendCommand("Security.enable");
auto has_certificate = [](const base::Value& params) {
return params.FindListPath(
"visibleSecurityState.certificateSecurityState.certificate") !=
nullptr;
};
base::Value params = WaitForMatchingNotification(
"Security.visibleSecurityStateChanged", base::Bind(has_certificate));
// Verify that the visibleSecurityState payload matches the SSL status data.
std::string* security_state =
params.FindStringPath("visibleSecurityState.securityState");
ASSERT_TRUE(security_state);
ASSERT_EQ(std::string("secure"), *security_state);
base::Value* certificate_security_state =
params.FindPath("visibleSecurityState.certificateSecurityState");
ASSERT_TRUE(certificate_security_state);
std::string* protocol =
certificate_security_state->FindStringPath("protocol");
ASSERT_TRUE(protocol);
ASSERT_EQ(*protocol, page_protocol);
std::string* key_exchange =
certificate_security_state->FindStringPath("keyExchange");
ASSERT_TRUE(key_exchange);
ASSERT_EQ(*key_exchange, page_key_exchange);
std::string* key_exchange_group =
certificate_security_state->FindStringPath("keyExchangeGroup");
if (key_exchange_group) {
ASSERT_EQ(*key_exchange_group, page_key_exchange_group);
}
std::string* mac = certificate_security_state->FindStringPath("mac");
if (mac) {
ASSERT_EQ(*mac, page_mac);
}
std::string* cipher = certificate_security_state->FindStringPath("cipher");
ASSERT_TRUE(cipher);
ASSERT_EQ(*cipher, page_cipher);
std::string* subject_name =
certificate_security_state->FindStringPath("subjectName");
ASSERT_TRUE(subject_name);
ASSERT_EQ(*subject_name, page_subject_name);
std::string* issuer = certificate_security_state->FindStringPath("issuer");
ASSERT_TRUE(issuer);
ASSERT_EQ(*issuer, page_issuer_name);
auto valid_from = certificate_security_state->FindDoublePath("validFrom");
ASSERT_TRUE(valid_from);
ASSERT_EQ(*valid_from, page_valid_from);
auto valid_to = certificate_security_state->FindDoublePath("validTo");
ASSERT_TRUE(valid_to);
ASSERT_EQ(*valid_to, page_valid_to);
auto certificate_has_weak_signature =
certificate_security_state->FindBoolPath("certifcateHasWeakSignature");
ASSERT_TRUE(certificate_has_weak_signature);
ASSERT_EQ(*certificate_has_weak_signature,
page_certificate_has_weak_signature);
auto modern_ssl = certificate_security_state->FindBoolPath("modernSSL");
ASSERT_TRUE(modern_ssl);
ASSERT_EQ(*modern_ssl, page_modern_ssl);
auto obsolete_ssl_protocol =
certificate_security_state->FindBoolPath("obsoleteSslProtocol");
ASSERT_TRUE(obsolete_ssl_protocol);
ASSERT_EQ(*obsolete_ssl_protocol, page_obsolete_ssl_protocol);
auto obsolete_ssl_key_exchange =
certificate_security_state->FindBoolPath("obsoleteSslKeyExchange");
ASSERT_TRUE(obsolete_ssl_key_exchange);
ASSERT_EQ(*obsolete_ssl_key_exchange, page_obsolete_ssl_key_exchange);
auto obsolete_ssl_cipher =
certificate_security_state->FindBoolPath("obsoleteSslCipher");
ASSERT_TRUE(obsolete_ssl_cipher);
ASSERT_EQ(*obsolete_ssl_cipher, page_obsolete_ssl_cipher);
auto obsolete_ssl_signature =
certificate_security_state->FindBoolPath("obsoleteSslSignature");
ASSERT_TRUE(obsolete_ssl_signature);
ASSERT_EQ(*obsolete_ssl_signature, page_obsolete_ssl_signature);
const base::Value* certificate_value =
certificate_security_state->FindListPath("certificate");
std::vector<std::string> der_certs;
for (const auto& cert : certificate_value->GetList()) {
std::string decoded;
ASSERT_TRUE(base::Base64Decode(cert.GetString(), &decoded));
der_certs.push_back(decoded);
}
std::vector<base::StringPiece> cert_string_piece;
for (const auto& str : der_certs) {
cert_string_piece.push_back(str);
}
// Check that the certificateSecurityState.certificate matches.
net::SHA256HashValue page_cert_chain_fingerprint =
page_cert->CalculateChainFingerprint256();
scoped_refptr<net::X509Certificate> certificate =
net::X509Certificate::CreateFromDERCertChain(cert_string_piece);
ASSERT_TRUE(certificate);
EXPECT_EQ(page_cert_chain_fingerprint,
certificate->CalculateChainFingerprint256());
const base::Value* security_state_issue_ids =
params.FindListPath("visibleSecurityState.securityStateIssueIds");
EXPECT_EQ(security_state_issue_ids->GetList().size(), 0u);
}