blob: 2c6af680fe100e1d98910fa11a8ef7175d2ba9ea [file] [log] [blame]
// Copyright 2016 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/ssl/chrome_expect_ct_reporter.h"
#include <string>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/common/chrome_features.h"
#include "net/url_request/report_sender.h"
namespace {
std::string TimeToISO8601(const base::Time& t) {
base::Time::Exploded exploded;
t.UTCExplode(&exploded);
return base::StringPrintf(
"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month,
exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
exploded.millisecond);
}
std::unique_ptr<base::ListValue> GetPEMEncodedChainAsList(
const net::X509Certificate* cert_chain) {
if (!cert_chain)
return base::MakeUnique<base::ListValue>();
std::unique_ptr<base::ListValue> result(new base::ListValue());
std::vector<std::string> pem_encoded_chain;
cert_chain->GetPEMEncodedChain(&pem_encoded_chain);
for (const std::string& cert : pem_encoded_chain)
result->Append(base::MakeUnique<base::Value>(cert));
return result;
}
std::string SCTOriginToString(
net::ct::SignedCertificateTimestamp::Origin origin) {
switch (origin) {
case net::ct::SignedCertificateTimestamp::SCT_EMBEDDED:
return "embedded";
case net::ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION:
return "from-tls-extension";
case net::ct::SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE:
return "from-ocsp-response";
default:
NOTREACHED();
}
return "";
}
void AddUnknownSCT(
const net::SignedCertificateTimestampAndStatus& sct_and_status,
base::ListValue* list) {
std::unique_ptr<base::DictionaryValue> list_item(new base::DictionaryValue());
list_item->SetString("origin", SCTOriginToString(sct_and_status.sct->origin));
list->Append(std::move(list_item));
}
void AddInvalidSCT(
const net::SignedCertificateTimestampAndStatus& sct_and_status,
base::ListValue* list) {
std::unique_ptr<base::DictionaryValue> list_item(new base::DictionaryValue());
list_item->SetString("origin", SCTOriginToString(sct_and_status.sct->origin));
std::string log_id;
base::Base64Encode(sct_and_status.sct->log_id, &log_id);
list_item->SetString("id", log_id);
list->Append(std::move(list_item));
}
void AddValidSCT(const net::SignedCertificateTimestampAndStatus& sct_and_status,
base::ListValue* list) {
std::unique_ptr<base::DictionaryValue> list_item(new base::DictionaryValue());
list_item->SetString("origin", SCTOriginToString(sct_and_status.sct->origin));
// The structure of the SCT object is defined in
// http://tools.ietf.org/html/rfc6962#section-4.1.
std::unique_ptr<base::DictionaryValue> sct(new base::DictionaryValue());
sct->SetInteger("sct_version", sct_and_status.sct->version);
std::string log_id;
base::Base64Encode(sct_and_status.sct->log_id, &log_id);
sct->SetString("id", log_id);
base::TimeDelta timestamp =
sct_and_status.sct->timestamp - base::Time::UnixEpoch();
sct->SetString("timestamp", base::Int64ToString(timestamp.InMilliseconds()));
std::string extensions;
base::Base64Encode(sct_and_status.sct->extensions, &extensions);
sct->SetString("extensions", extensions);
std::string signature;
base::Base64Encode(sct_and_status.sct->signature.signature_data, &signature);
sct->SetString("signature", signature);
list_item->Set("sct", std::move(sct));
list->Append(std::move(list_item));
}
// Records an UMA histogram of the net errors when Expect CT reports
// fail to send.
void RecordUMAOnFailure(const GURL& report_uri,
int net_error,
int http_response_code) {
UMA_HISTOGRAM_SPARSE_SLOWLY("SSL.ExpectCTReportFailure2", -net_error);
}
} // namespace
ChromeExpectCTReporter::ChromeExpectCTReporter(
net::URLRequestContext* request_context)
: report_sender_(new net::ReportSender(request_context)) {}
ChromeExpectCTReporter::~ChromeExpectCTReporter() {}
void ChromeExpectCTReporter::OnExpectCTFailed(
const net::HostPortPair& host_port_pair,
const GURL& report_uri,
const net::X509Certificate* validated_certificate_chain,
const net::X509Certificate* served_certificate_chain,
const net::SignedCertificateTimestampAndStatusList&
signed_certificate_timestamps) {
if (report_uri.is_empty())
return;
if (!base::FeatureList::IsEnabled(features::kExpectCTReporting))
return;
// TODO(estark): De-duplicate reports so that the same report isn't
// sent too often in some period of time.
base::DictionaryValue report;
report.SetString("hostname", host_port_pair.host());
report.SetInteger("port", host_port_pair.port());
report.SetString("date-time", TimeToISO8601(base::Time::Now()));
report.Set("served-certificate-chain",
GetPEMEncodedChainAsList(served_certificate_chain));
report.Set("validated-certificate-chain",
GetPEMEncodedChainAsList(validated_certificate_chain));
std::unique_ptr<base::ListValue> unknown_scts(new base::ListValue());
std::unique_ptr<base::ListValue> invalid_scts(new base::ListValue());
std::unique_ptr<base::ListValue> valid_scts(new base::ListValue());
for (const auto& sct_and_status : signed_certificate_timestamps) {
switch (sct_and_status.status) {
case net::ct::SCT_STATUS_LOG_UNKNOWN:
AddUnknownSCT(sct_and_status, unknown_scts.get());
break;
case net::ct::SCT_STATUS_INVALID_SIGNATURE:
case net::ct::SCT_STATUS_INVALID_TIMESTAMP:
AddInvalidSCT(sct_and_status, invalid_scts.get());
break;
case net::ct::SCT_STATUS_OK:
AddValidSCT(sct_and_status, valid_scts.get());
break;
default:
NOTREACHED();
}
}
report.Set("unknown-scts", std::move(unknown_scts));
report.Set("invalid-scts", std::move(invalid_scts));
report.Set("valid-scts", std::move(valid_scts));
std::string serialized_report;
if (!base::JSONWriter::Write(report, &serialized_report)) {
LOG(ERROR) << "Failed to serialize Expect CT report";
return;
}
UMA_HISTOGRAM_BOOLEAN("SSL.ExpectCTReportSendingAttempt", true);
report_sender_->Send(report_uri, "application/json; charset=utf-8",
serialized_report, base::Callback<void()>(),
base::Bind(RecordUMAOnFailure));
}