Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions components/p3a/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ static_library("p3a") {
"star_randomness_meta.h",
"star_randomness_points.cc",
"star_randomness_points.h",
"star_url_loader_network_service_observer.cc",
"star_url_loader_network_service_observer.h",
"switches.h",
"uploader.cc",
"uploader.h",
Expand Down
2 changes: 1 addition & 1 deletion components/p3a/features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ BASE_FEATURE(kConstellation,
base::FEATURE_ENABLED_BY_DEFAULT);
// Verify Constellation randomness server secure enclave certificate.
BASE_FEATURE(kConstellationEnclaveAttestation,
"BraveP3AConstellationEnclaveAttestation",
"BraveP3AConstellationEnclaveAttestationV2",
base::FEATURE_DISABLED_BY_DEFAULT);
// Disable reporting answers over direct https+json
// for typical (weekly) cadence P3A questions.
Expand Down
62 changes: 25 additions & 37 deletions components/p3a/nitro_utils/attestation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "brave/components/p3a/nitro_utils/cose.h"
#include "brave/components/p3a/star_url_loader_network_service_observer.h"
#include "components/cbor/reader.h"
#include "crypto/random.h"
#include "net/base/url_util.h"
Expand Down Expand Up @@ -54,13 +55,6 @@ constexpr net::SHA256HashValue kAWSRootCertFP{
0x31, 0x95, 0xD6, 0x06, 0x31, 0x7E, 0xD7, 0xCD, 0xCC, 0x3C, 0x17,
0x56, 0xE0, 0x98, 0x93, 0xF3, 0xC6, 0x8F, 0x79, 0xBB, 0x5B}};

// Old-style user_data is a pair of prefix:<binary digest> values
// separated by semicolons. The first value is the TLS cert fingerprint.
constexpr char kHashPrefix[] = "sha256:";
constexpr size_t kHashPrefixLength = sizeof(kHashPrefix) - 1;
constexpr size_t kUserDataOldLength =
2 * (kSHA256HashLength + kHashPrefixLength) + 1;

net::NetworkTrafficAnnotationTag AttestationAnnotation() {
return net::DefineNetworkTrafficAnnotation("nitro_utils_attestation", R"(
semantics {
Expand Down Expand Up @@ -115,36 +109,22 @@ bool VerifyUserDataKey(scoped_refptr<net::X509Certificate> server_cert,
const net::SHA256HashValue server_cert_fp =
net::X509Certificate::CalculateFingerprint256(server_cert->cert_buffer());

// The hashes in the old and new user_data schemes have incommensurate
// lengths, so use the total length to distinguish between them.
if (user_data_bytes.size() == kUserDataOldLength) {
if (memcmp(user_data_bytes.data(), kHashPrefix, kHashPrefixLength) != 0) {
LOG(ERROR)
<< "Nitro verification: user data is missing sha256 hash prefix";
return false;
}
if (memcmp(server_cert_fp.data, user_data_bytes.data() + kHashPrefixLength,
kSHA256HashLength) == 0) {
return true;
}
} else {
// Look for the TLS cert fingerprint as a multihash.
if (user_data_bytes.size() < kUserDataMinLength) {
LOG(ERROR) << "Nitro verification: user data is not at least "
<< kUserDataMinLength << " bytes";
return false;
}
// We only support sha2-256 fingerprints.
if (user_data_bytes[0] != kMultihashSHA256Code &&
user_data_bytes[1] != kSHA256HashLength) {
LOG(ERROR) << "Nitro verification: user data not a sha2-256 multihash";
return false;
}
if (memcmp(server_cert_fp.data,
user_data_bytes.data() + kMultihashPrefixLength,
kSHA256HashLength) == 0) {
return true;
}
// Look for the TLS cert fingerprint as a multihash.
if (user_data_bytes.size() < kUserDataMinLength) {
LOG(ERROR) << "Nitro verification: user data is not at least "
<< kUserDataMinLength << " bytes";
return false;
}
// We only support sha2-256 fingerprints.
if (user_data_bytes[0] != kMultihashSHA256Code &&
user_data_bytes[1] != kSHA256HashLength) {
LOG(ERROR) << "Nitro verification: user data not a sha2-256 multihash";
return false;
}
if (memcmp(server_cert_fp.data,
user_data_bytes.data() + kMultihashPrefixLength,
kSHA256HashLength) == 0) {
return true;
}
LOG(ERROR)
<< "Nitro verification: server cert fp does not match user data fp, "
Expand Down Expand Up @@ -281,6 +261,8 @@ void ParseAndVerifyDocument(
void RequestAndVerifyAttestationDocument(
const GURL& attestation_url,
network::mojom::URLLoaderFactory* url_loader_factory,
p3a::StarURLLoaderNetworkServiceObserver*
attestation_network_service_observer,
base::OnceCallback<void(scoped_refptr<net::X509Certificate>)>
result_callback) {
std::vector<uint8_t> nonce(20);
Expand All @@ -291,6 +273,12 @@ void RequestAndVerifyAttestationDocument(
std::string nonce_hex = base::ToLowerASCII(base::HexEncode(nonce));
resource_request->url =
net::AppendQueryParameter(attestation_url, "nonce", nonce_hex);
if (attestation_network_service_observer) {
resource_request->trusted_params =
std::make_optional<network::ResourceRequest::TrustedParams>();
resource_request->trusted_params->url_loader_network_observer =
attestation_network_service_observer->Bind();
}

std::unique_ptr<network::SimpleURLLoader> url_loader =
network::SimpleURLLoader::Create(std::move(resource_request),
Expand Down
3 changes: 3 additions & 0 deletions components/p3a/nitro_utils/attestation.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <vector>

#include "base/functional/callback.h"
#include "brave/components/p3a/star_url_loader_network_service_observer.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"

Expand All @@ -25,6 +26,8 @@ namespace nitro_utils {
void RequestAndVerifyAttestationDocument(
const GURL& attestation_url,
network::mojom::URLLoaderFactory* url_loader_factory,
p3a::StarURLLoaderNetworkServiceObserver*
attestation_network_service_observer,
base::OnceCallback<void(scoped_refptr<net::X509Certificate>)>
result_callback);

Expand Down
34 changes: 0 additions & 34 deletions components/p3a/nitro_utils/attestation_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,6 @@ constexpr char kBadDocument[] =
"yxyk5Gl1c2VyX2RhdGFYIgogsn3PzVQFXXUPf6W5qVRUi3bVGRJ2C6PRri1YOt5YwqlqcHVi"
"bGljX2tlefY=";

// Version of the attestation document with older user_data scheme
constexpr char kOldDocument[] =
"pmVub25jZVShkGajO9w8jptiAlBSISId8fsW9WZkaWdlc3RmU0hBMzg0aW1vZHVs"
"ZV9pZHgnaS0wYjQ2MzcxODcxOWFkYjI0YS1lbmMwMTg4OTc4NjRhYmFlNWQyaXRp"
"bWVzdGFtcBsAAAGIyxyk5Gl1c2VyX2RhdGFYT3NoYTI1NjpDAdOabr43snNXvkv/"
"2MrFUkqawkwDXxM30KIg0PKpejtzaGEyNTY6AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAABqcHVibGljX2tlefY=";

// Version of the attestation document with randomized older scheme
// user_data and randomized nonce
constexpr char kOldBadDocument[] =
"pmVub25jZVSYQjMTB+E+8zgs690i18gcDtPF5WZkaWdlc3RmU0hBMzg0aW1vZHVs"
"ZV9pZHgnaS0wYjQ2MzcxODcxOWFkYjI0YS1lbmMwMTg4OTc4NjRhYmFlNWQyaXRp"
"bWVzdGFtcBsAAAGIyxyk5Gl1c2VyX2RhdGFYT3NoYTI1NjpZ1l7mBoYUN/QYaRUP"
"4qHov+2saZzBKCCBT16rA6fp6DtzaGEyNTY6psMTTAVeLx8shS1642BWxoW1q/dV"
"RMvN0XG/pFkT4cVqcHVibGljX2tlefY=";

// TLS certificate from the same deployment as kShortDocument
// Certificates can be downloaded with the browser or on the command line:
// openssl s_client -showcerts -servername $HOST -connect $HOST:443 < /dev/null
Expand Down Expand Up @@ -124,21 +107,4 @@ TEST_F(NitroAttestationTest, UserData) {
EXPECT_FALSE(VerifyUserDataKeyForTesting(bad, cert));
}

TEST_F(NitroAttestationTest, OldUserData) {
// Parse a TLS cert whose fingerprint matches the test
// attestation document.
auto cert_bytes = FromBase64(kTLSCert);
auto cert = net::X509Certificate::CreateFromBytes(cert_bytes);
ASSERT_TRUE(cert);

// Does the old scheme document verify against the expected
// cert fingerprint?
auto old = FromBase64(kOldDocument);
EXPECT_TRUE(VerifyUserDataKeyForTesting(old, cert));

// Does the invalid document fail to verify against the same cert?
auto bad = FromBase64(kOldBadDocument);
EXPECT_FALSE(VerifyUserDataKeyForTesting(bad, cert));
}

} // namespace nitro_utils
10 changes: 8 additions & 2 deletions components/p3a/p3a_config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "base/strings/string_number_conversions.h"
#include "brave/brave_domains/service_domains.h"
#include "brave/components/p3a/buildflags.h"
#include "brave/components/p3a/features.h"
#include "brave/components/p3a/metric_log_type.h"
#include "brave/components/p3a/switches.h"
#include "url/url_constants.h"
Expand All @@ -30,6 +31,7 @@ constexpr char kP2AJsonHostPrefix[] = "p2a-json";
constexpr char kJsonURLPath[] = "/";
constexpr char kConstellationCollectorHostPrefix[] = "collector.bsg";
constexpr char kRandomnessHostPrefix[] = "star-randsrv.bsg";
constexpr char kRandomnessHostV2Prefix[] = "star-randsrv-v2.bsg";

base::TimeDelta MaybeOverrideTimeDeltaFromCommandLine(
base::CommandLine* cmdline,
Expand Down Expand Up @@ -112,8 +114,12 @@ P3AConfig::P3AConfig()
GetDefaultURL(kP3ACreativeHostPrefix, kJsonURLPath)),
p2a_json_upload_url(GetDefaultURL(kP2AJsonHostPrefix, kJsonURLPath)),
p3a_constellation_upload_host(
GetDefaultHost(kConstellationCollectorHostPrefix)),
star_randomness_host(GetDefaultHost(kRandomnessHostPrefix)) {
GetDefaultHost(kConstellationCollectorHostPrefix)) {
disable_star_attestation =
!features::IsConstellationEnclaveAttestationEnabled();
star_randomness_host =
GetDefaultHost(disable_star_attestation ? kRandomnessHostPrefix
: kRandomnessHostV2Prefix);
CheckURL(p3a_json_upload_url);
CheckURL(p3a_creative_upload_url);
CheckURL(p2a_json_upload_url);
Expand Down
4 changes: 0 additions & 4 deletions components/p3a/p3a_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,4 @@ void P3AService::HandleHistogramChange(
only_update_for_constellation);
}

void P3AService::DisableStarAttestationForTesting() {
config_.disable_star_attestation = true;
}

} // namespace p3a
2 changes: 0 additions & 2 deletions components/p3a/p3a_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ class P3AService : public base::RefCountedThreadSafe<P3AService>,
std::optional<MetricLogType> GetDynamicMetricLogType(
const std::string& histogram_name) const override;

void DisableStarAttestationForTesting();

private:
friend class base::RefCountedThreadSafe<P3AService>;
~P3AService() override;
Expand Down
2 changes: 1 addition & 1 deletion components/p3a/p3a_service_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class P3AServiceTest : public testing::Test {
config_.p3a_json_upload_url = GURL(kTestP3AJsonHost);
config_.p2a_json_upload_url = GURL(kTestP2AJsonHost);
config_.p3a_creative_upload_url = GURL(kTestP3ACreativeHost);
config_.disable_star_attestation = true;
}

void TearDown() override { p3a_service_ = nullptr; }
Expand All @@ -88,7 +89,6 @@ class P3AServiceTest : public testing::Test {
p3a_service_ = scoped_refptr(new P3AService(
local_state_, "release", "2049-01-01", P3AConfig(config_)));

p3a_service_->DisableStarAttestationForTesting();
p3a_service_->Init(shared_url_loader_factory_);
task_environment_.RunUntilIdle();
}
Expand Down
91 changes: 39 additions & 52 deletions components/p3a/star_randomness_meta.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,23 @@ StarRandomnessMeta::StarRandomnessMeta(
for (MetricLogType log_type : kAllMetricLogTypes) {
update_states_[log_type] = std::make_unique<RandomnessServerUpdateState>();
}
std::string approved_cert_fp_str =
local_state->GetString(kApprovedCertFPPrefName);
net::HashValue approved_cert_fp;
if (!approved_cert_fp_str.empty() &&
approved_cert_fp.FromString(approved_cert_fp_str)) {
VLOG(2) << "StarRandomnessMeta: loaded cached approved cert";
approved_cert_fp_ = std::make_optional(approved_cert_fp);
if (!config_->disable_star_attestation) {
attestation_network_service_observer_ =
std::make_unique<StarURLLoaderNetworkServiceObserver>(
true, base::DoNothing());
normal_network_service_observer_ =
std::make_unique<StarURLLoaderNetworkServiceObserver>(
false, base::BindRepeating(&StarRandomnessMeta::AttestServer,
base::Unretained(this), false));
std::string approved_cert_fp_str =
local_state->GetString(kApprovedCertFPPrefName);
net::HashValue approved_cert_fp;
if (!approved_cert_fp_str.empty() &&
approved_cert_fp.FromString(approved_cert_fp_str)) {
VLOG(2) << "StarRandomnessMeta: loaded cached approved cert";
normal_network_service_observer_->SetApprovedCertFingerprint(
approved_cert_fp);
}
}
}

Expand Down Expand Up @@ -150,46 +160,12 @@ void StarRandomnessMeta::MigrateObsoleteLocalStatePrefs(
}
}

bool StarRandomnessMeta::ShouldAttestEnclave() {
return !config_->disable_star_attestation &&
features::IsConstellationEnclaveAttestationEnabled();
}

bool StarRandomnessMeta::VerifyRandomnessCert(
network::SimpleURLLoader* url_loader) {
if (!ShouldAttestEnclave()) {
VLOG(2) << "StarRandomnessMeta: skipping approved cert check";
return true;
}
const network::mojom::URLResponseHead* response_info =
url_loader->ResponseInfo();
if (!approved_cert_fp_.has_value()) {
LOG(ERROR) << "StarRandomnessMeta: approved cert is missing";
AttestServer(false);
return false;
}
if (!response_info->ssl_info.has_value() ||
response_info->ssl_info->cert == nullptr) {
LOG(ERROR) << "StarRandomnessMeta: ssl info is missing from response info";
return false;
}
net::HashValue cert_fp_hash = net::HashValue(
response_info->ssl_info->cert->CalculateChainFingerprint256());
if (cert_fp_hash != *approved_cert_fp_) {
LOG(ERROR) << "StarRandomnessMeta: approved cert mismatch, will retry "
"attestation; fp = "
<< cert_fp_hash.ToString();
AttestServer(false);
return false;
}
return true;
}

void StarRandomnessMeta::RequestServerInfo(MetricLogType log_type) {
RandomnessServerUpdateState* update_state = update_states_[log_type].get();
update_state->rnd_server_info = nullptr;

if (ShouldAttestEnclave() && !approved_cert_fp_.has_value()) {
if (normal_network_service_observer_ &&
!normal_network_service_observer_->HasApprovedCert()) {
AttestServer(true);
return;
}
Expand Down Expand Up @@ -240,11 +216,18 @@ void StarRandomnessMeta::RequestServerInfo(MetricLogType log_type) {
"server info request";
return;
}
if (normal_network_service_observer_) {
resource_request->trusted_params =
std::make_optional<network::ResourceRequest::TrustedParams>();
resource_request->trusted_params->url_loader_network_observer =
normal_network_service_observer_->Bind();
}

update_state->url_loader = network::SimpleURLLoader::Create(
std::move(resource_request), GetRandomnessRequestAnnotation());
update_state->url_loader->SetURLLoaderFactoryOptions(
network::mojom::kURLLoadOptionSendSSLInfoWithResponse);
network::mojom::kURLLoadOptionSendSSLInfoWithResponse |
network::mojom::kURLLoadOptionSendSSLInfoForCertificateError);
update_state->url_loader->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&StarRandomnessMeta::HandleServerInfoResponse,
Expand All @@ -257,12 +240,17 @@ RandomnessServerInfo* StarRandomnessMeta::GetCachedRandomnessServerInfo(
return update_states_[log_type]->rnd_server_info.get();
}

StarURLLoaderNetworkServiceObserver*
StarRandomnessMeta::GetURLLoaderNetworkServiceObserver() {
return normal_network_service_observer_.get();
}

void StarRandomnessMeta::AttestServer(bool make_info_request_after) {
if (attestation_pending_) {
return;
}
attestation_pending_ = true;
approved_cert_fp_ = std::nullopt;
normal_network_service_observer_->SetApprovedCertFingerprint(std::nullopt);

VLOG(2) << "StarRandomnessMeta: starting attestation";
GURL attestation_url = GURL(
Expand All @@ -274,6 +262,7 @@ void StarRandomnessMeta::AttestServer(bool make_info_request_after) {
}
nitro_utils::RequestAndVerifyAttestationDocument(
attestation_url, url_loader_factory_.get(),
attestation_network_service_observer_.get(),
base::BindOnce(&StarRandomnessMeta::HandleAttestationResult,
weak_ptr_factory_.GetWeakPtr(), make_info_request_after));
}
Expand All @@ -291,9 +280,11 @@ void StarRandomnessMeta::HandleAttestationResult(
}
return;
}
approved_cert_fp_ = std::make_optional(
net::HashValue(approved_cert->CalculateChainFingerprint256()));
std::string approved_cert_fp_str = approved_cert_fp_->ToString();
auto approved_cert_fp =
net::HashValue(approved_cert->CalculateChainFingerprint256());
std::string approved_cert_fp_str = approved_cert_fp.ToString();
normal_network_service_observer_->SetApprovedCertFingerprint(
approved_cert_fp);
local_state_->SetString(kApprovedCertFPPrefName, approved_cert_fp_str);
attestation_pending_ = false;
VLOG(2) << "StarRandomnessMeta: attestation succeeded; fp = "
Expand Down Expand Up @@ -339,10 +330,6 @@ void StarRandomnessMeta::HandleServerInfoResponse(
ScheduleServerInfoRetry(log_type);
return;
}
if (!VerifyRandomnessCert(update_state->url_loader.get())) {
ScheduleServerInfoRetry(log_type);
return;
}
update_state->url_loader = nullptr;
base::JSONReader::Result parsed_value =
base::JSONReader::ReadAndReturnValueWithError(
Expand Down
Loading