blob: 046ccd8e319e0c0013fb68e44d5eff13d9090bea [file] [log] [blame]
// Copyright 2021 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/media/cdm_pref_service_helper.h"
#include "base/logging.h"
#include "base/base64.h"
#include "base/json/values_util.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
const char kOriginId[] = "origin_id";
const char kOriginIdCreationTime[] = "origin_id_creation_time";
const char kClientToken[] = "client_token";
const char kClientTokenCreationTime[] = "client_token_creation_time";
bool TimeIsBetween(const base::Time& time,
const base::Time& start,
const base::Time& end) {
return time >= start && (end.is_null() || time <= end);
}
// Data stored in the kMediaCdmOriginData Pref dictionary.
// {
// $origin_string: {
// # A unique random string for the real "origin_string".
// "origin_id": $origin_id
// "origin_id_creation_time": $origin_id_creation_time
// "client_token": $client_token (optional)
// "client_token_creation_time": $client_token_creation_time (optional)
// },
// more origin_string map...
// }
base::Value ToDictValue(const CdmPrefData& pref_data) {
base::Value dict(base::Value::Type::DICTIONARY);
// Origin ID
dict.SetKey(kOriginId, base::UnguessableTokenToValue(pref_data.origin_id()));
dict.SetKey(kOriginIdCreationTime,
base::TimeToValue(pref_data.origin_id_creation_time()));
// Optional Client Token
const absl::optional<std::vector<uint8_t>> client_token =
pref_data.client_token();
if (client_token.has_value() && !client_token->empty()) {
std::string encoded_client_token = base::Base64Encode(client_token.value());
dict.SetStringKey(kClientToken, encoded_client_token);
dict.SetKey(kClientTokenCreationTime,
base::TimeToValue(pref_data.client_token_creation_time()));
}
return dict;
}
// Convert `cdm_data_dict` to CdmPrefData. `cdm_data_dict` contains the origin
// id and the time it was first created as well as the client token and the time
// it was set/updated. Return nullptr if `cdm_data_dict` has any corruption,
// e.g. format error, missing fields, invalid value.
std::unique_ptr<CdmPrefData> FromDictValue(const base::Value& cdm_data_dict) {
DCHECK(cdm_data_dict.is_dict());
// Origin ID
const base::Value* origin_id_value = cdm_data_dict.FindKey(kOriginId);
if (!origin_id_value)
return nullptr;
absl::optional<base::UnguessableToken> origin_id =
base::ValueToUnguessableToken(*origin_id_value);
if (!origin_id)
return nullptr;
const base::Value* time_value = cdm_data_dict.FindKey(kOriginIdCreationTime);
if (!time_value)
return nullptr;
absl::optional<base::Time> origin_id_time = base::ValueToTime(time_value);
if (!origin_id_time || origin_id_time.value().is_null())
return nullptr;
auto cdm_pref_data =
std::make_unique<CdmPrefData>(origin_id.value(), origin_id_time.value());
// Client Token
const std::string* encoded_client_token =
cdm_data_dict.FindStringKey(kClientToken);
if (encoded_client_token) {
std::string decoded_client_token;
if (!base::Base64Decode(*encoded_client_token, &decoded_client_token))
return nullptr;
std::vector<uint8_t> client_token(decoded_client_token.begin(),
decoded_client_token.end());
time_value = cdm_data_dict.FindKey(kClientTokenCreationTime);
// If we have a client token but no creation time, this is an error.
if (!time_value)
return nullptr;
absl::optional<base::Time> client_token_time =
base::ValueToTime(time_value);
if (!client_token_time)
return nullptr;
cdm_pref_data->SetClientToken(client_token, client_token_time.value());
}
return cdm_pref_data;
}
} // namespace
CdmPrefData::CdmPrefData(const base::UnguessableToken& origin_id,
base::Time origin_id_time)
: origin_id_(origin_id), origin_id_creation_time_(origin_id_time) {
DCHECK(origin_id_);
}
CdmPrefData::~CdmPrefData() = default;
const base::UnguessableToken& CdmPrefData::origin_id() const {
return origin_id_;
}
base::Time CdmPrefData::origin_id_creation_time() const {
return origin_id_creation_time_;
}
const absl::optional<std::vector<uint8_t>> CdmPrefData::client_token() const {
return client_token_;
}
base::Time CdmPrefData::client_token_creation_time() const {
return client_token_creation_time_;
}
void CdmPrefData::SetClientToken(const std::vector<uint8_t>& client_token,
const base::Time creation_time) {
VLOG(1) << __func__;
client_token_ = client_token;
client_token_creation_time_ = creation_time;
}
CdmPrefServiceHelper::CdmPrefServiceHelper() = default;
CdmPrefServiceHelper::~CdmPrefServiceHelper() = default;
void CdmPrefServiceHelper::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kMediaCdmOriginData);
}
// Removes the CDM preference data from origin dict if the session's creation
// time falls in [`start`, `end`] and `filter` returns true on its origin.
// `start` can be null, which would indicate that we should delete everything
// since the beginning of time. `end` can also be null, in which case we can
// just ignore it. If only `client_token_creation_time` falls between `start`
// and `end`, we only clear that field. If `origin_id_creation_time` falls
// between `start` and `end`, we clear the whole entry.
void CdmPrefServiceHelper::ClearCdmPreferenceData(
PrefService* user_prefs,
base::Time start,
base::Time end,
const base::RepeatingCallback<bool(const GURL&)>& filter) {
DVLOG(1) << __func__ << " From [" << start << ", " << end << "]";
DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
std::vector<std::string> origins_to_delete;
for (auto key_value : update->DictItems()) {
const std::string& origin = key_value.first;
// Null filter indicates that we should delete everything.
if (filter && !filter.Run(GURL(origin)))
continue;
const base::Value& origin_dict = key_value.second;
if (!origin_dict.is_dict()) {
DVLOG(ERROR) << "Could not parse the preference data. Removing entry.";
origins_to_delete.push_back(origin);
continue;
}
std::unique_ptr<CdmPrefData> cdm_pref_data = FromDictValue(origin_dict);
if (!cdm_pref_data) {
origins_to_delete.push_back(origin);
continue;
}
if (TimeIsBetween(cdm_pref_data->origin_id_creation_time(), start, end)) {
DVLOG(1) << "Clearing cdm pref data for " << origin;
origins_to_delete.push_back(origin);
} else if (TimeIsBetween(cdm_pref_data->client_token_creation_time(), start,
end)) {
key_value.second.RemoveKey(kClientToken);
key_value.second.RemoveKey(kClientTokenCreationTime);
}
}
// Remove CDM preference data.
for (const auto& origin_str : origins_to_delete)
update->RemoveKey(origin_str);
DVLOG(1) << __func__ << "Done removing CDM preference data";
}
std::unique_ptr<CdmPrefData> CdmPrefServiceHelper::GetCdmPrefData(
PrefService* user_prefs,
const url::Origin& cdm_origin) {
VLOG(1) << __func__;
// Access to the PrefService must be made from the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* dict =
user_prefs->GetDictionary(prefs::kMediaCdmOriginData);
DCHECK(!cdm_origin.opaque());
if (cdm_origin.opaque()) {
mojo::ReportBadMessage("EME use is not allowed on opaque origin");
return nullptr;
}
const std::string serialized_cdm_origin = cdm_origin.Serialize();
DCHECK(!serialized_cdm_origin.empty());
const base::Value* cdm_data_dict =
dict->FindKeyOfType(serialized_cdm_origin, base::Value::Type::DICTIONARY);
std::unique_ptr<CdmPrefData> cdm_pref_data;
if (cdm_data_dict)
cdm_pref_data = FromDictValue(*cdm_data_dict);
// Create an new entry or overwrite the existing one in case we weren't able
// to get a valid origin ID from `FromDictValue()`.
if (!cdm_pref_data) {
DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
base::Value* update_dict = update.Get();
cdm_pref_data = std::make_unique<CdmPrefData>(
base::UnguessableToken::Create(), base::Time::Now());
update_dict->SetKey(serialized_cdm_origin, ToDictValue(*cdm_pref_data));
}
return cdm_pref_data;
}
void CdmPrefServiceHelper::SetCdmClientToken(
PrefService* user_prefs,
const url::Origin& cdm_origin,
const std::vector<uint8_t>& client_token) {
VLOG(1) << __func__;
// Access to the PrefService must be made from the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!cdm_origin.opaque());
const std::string serialized_cdm_origin = cdm_origin.Serialize();
DCHECK(!serialized_cdm_origin.empty());
DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
base::Value* dict = update.Get();
base::Value* dict_value =
dict->FindKeyOfType(serialized_cdm_origin, base::Value::Type::DICTIONARY);
if (!dict_value) {
// If there is no preference associated with the origin at this point, this
// means that the preference data was deleted by the user recently. No need
// to save the client token in that case.
return;
}
std::unique_ptr<CdmPrefData> cdm_pref_data = FromDictValue(*dict_value);
if (!cdm_pref_data) {
DVLOG(ERROR) << "The CDM preference data for origin \""
<< serialized_cdm_origin
<< "\" could not be parsed. Removing entry from preferences.";
dict->RemoveKey(serialized_cdm_origin);
return;
}
cdm_pref_data->SetClientToken(client_token, base::Time::Now());
dict->SetKey(serialized_cdm_origin, ToDictValue(*cdm_pref_data));
}
std::map<std::string, url::Origin> CdmPrefServiceHelper::GetOriginIdMapping(
PrefService* user_prefs) {
std::map<std::string, url::Origin> mapping;
const base::Value* dict =
user_prefs->GetDictionary(prefs::kMediaCdmOriginData);
for (auto key_value : dict->DictItems()) {
const base::Value* origin_id_value = key_value.second.FindKey(kOriginId);
if (!origin_id_value)
continue;
absl::optional<base::UnguessableToken> origin_id =
base::ValueToUnguessableToken(*origin_id_value);
if (!origin_id)
continue;
const url::Origin origin = url::Origin::Create(GURL(key_value.first));
mapping[origin_id->ToString()] = origin;
}
return mapping;
}