1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/media_device_salt/media_device_salt_database.h"
7 #include "base/containers/cxx20_erase_vector.h"
8 #include "base/feature_list.h"
9 #include "base/functional/bind.h"
10 #include "base/strings/strcat.h"
11 #include "base/strings/string_util.h"
12 #include "base/time/time.h"
13 #include "sql/meta_table.h"
14 #include "sql/recovery.h"
15 #include "sql/statement.h"
16 #include "sql/transaction.h"
17 #include "third_party/blink/public/common/storage_key/storage_key.h"
19 namespace media_device_salt {
21 BASE_FEATURE(kMediaDeviceSaltDatabaseUseBuiltInRecoveryIfSupported,
22 "MediaDeviceSaltDatabaseUseBuiltInRecoveryIfSupported",
23 base::FEATURE_ENABLED_BY_DEFAULT);
26 // The current version of the database schema.
27 constexpr int kCurrentVersion = 1;
29 // The lowest version of the database schema such that old versions of the code
30 // can still read/write the current database.
31 constexpr int kCompatibleVersion = 1;
34 std::string CreateRandomSalt() {
35 return base::UnguessableToken::Create().ToString();
38 MediaDeviceSaltDatabase::MediaDeviceSaltDatabase(const base::FilePath& db_path)
40 db_(sql::DatabaseOptions{.exclusive_locking = true,
44 absl::optional<std::string> MediaDeviceSaltDatabase::GetOrInsertSalt(
45 const blink::StorageKey& storage_key,
46 absl::optional<std::string> candidate_salt) {
47 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
48 if (storage_key.origin().opaque() || !EnsureOpen()) {
51 sql::Transaction transaction(&db_);
52 if (!transaction.Begin()) {
55 static constexpr char kGetSaltSql[] =
56 "SELECT salt FROM media_device_salts WHERE storage_key=?";
57 DCHECK(db_.IsSQLValid(kGetSaltSql));
58 sql::Statement select_statement(
59 db_.GetCachedStatement(SQL_FROM_HERE, kGetSaltSql));
60 select_statement.BindString(0, storage_key.Serialize());
61 if (select_statement.Step()) {
62 return select_statement.ColumnString(0);
64 if (!select_statement.Succeeded()) {
68 static constexpr char kInsertSaltSql[] =
69 "INSERT INTO media_device_salts(storage_key,creation_time,salt) "
71 DCHECK(db_.IsSQLValid(kInsertSaltSql));
72 sql::Statement insert_statement(
73 db_.GetCachedStatement(SQL_FROM_HERE, kInsertSaltSql));
74 insert_statement.BindString(0, storage_key.Serialize());
75 insert_statement.BindTime(1, base::Time::Now());
76 std::string new_salt = candidate_salt.value_or(CreateRandomSalt());
77 insert_statement.BindString(2, new_salt);
78 return insert_statement.Run() && transaction.Commit()
79 ? absl::make_optional(new_salt)
83 void MediaDeviceSaltDatabase::DeleteEntries(
84 base::Time delete_begin,
85 base::Time delete_end,
86 content::StoragePartition::StorageKeyMatcherFunction matcher) {
87 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
92 if (matcher.is_null()) {
93 static constexpr char kDeleteSaltsSql[] =
94 "DELETE FROM media_device_salts "
95 "WHERE creation_time>=? AND creation_time<=?";
96 DCHECK(db_.IsSQLValid(kDeleteSaltsSql));
97 sql::Statement statement(db_.GetUniqueStatement(kDeleteSaltsSql));
98 statement.BindTime(0, delete_begin);
99 statement.BindTime(1, delete_end);
104 sql::Transaction transaction(&db_);
105 if (!transaction.Begin()) {
108 static constexpr char kGetStorageKeysSql[] =
109 "SELECT storage_key "
110 "FROM media_device_salts "
111 "WHERE creation_time>=? AND creation_time<=?";
112 DCHECK(db_.IsSQLValid(kGetStorageKeysSql));
113 sql::Statement select_statement(db_.GetUniqueStatement(kGetStorageKeysSql));
114 select_statement.BindTime(0, delete_begin);
115 select_statement.BindTime(1, delete_end);
117 std::vector<std::string> serialized_storage_keys;
118 while (select_statement.Step()) {
119 serialized_storage_keys.push_back(select_statement.ColumnString(0));
122 base::EraseIf(serialized_storage_keys,
123 [&matcher](const std::string& serialized_storage_key) {
124 absl::optional<blink::StorageKey> storage_key =
125 blink::StorageKey::Deserialize(serialized_storage_key);
126 if (!storage_key.has_value()) {
127 // This shouldn't happen, but include non-Deserializable
128 // keys for deletion if they're found.
131 return !matcher.Run(*storage_key);
134 if (serialized_storage_keys.empty()) {
138 const std::string delete_storage_keys_sql =
139 base::StrCat({"DELETE FROM media_device_salts "
140 "WHERE storage_key IN ('",
141 base::JoinString(serialized_storage_keys, "','"), "')"});
142 DCHECK(db_.IsSQLValid(delete_storage_keys_sql.c_str()));
143 sql::Statement delete_statement(
144 db_.GetUniqueStatement(delete_storage_keys_sql.c_str()));
145 delete_statement.Run() && transaction.Commit();
148 void MediaDeviceSaltDatabase::DeleteEntry(
149 const blink::StorageKey& storage_key) {
150 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
151 if (storage_key.origin().opaque() || !EnsureOpen()) {
154 static constexpr char kDeleteStorageKeySql[] =
155 "DELETE FROM media_device_salts "
156 "WHERE storage_key=?";
157 DCHECK(db_.IsSQLValid(kDeleteStorageKeySql));
158 sql::Statement statement(db_.GetUniqueStatement(kDeleteStorageKeySql));
159 statement.BindString(0, storage_key.Serialize());
163 std::vector<blink::StorageKey> MediaDeviceSaltDatabase::GetAllStorageKeys() {
164 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
168 std::vector<blink::StorageKey> storage_keys;
169 static constexpr char kGetStorageKeysSql[] =
170 "SELECT storage_key FROM media_device_salts";
171 DCHECK(db_.IsSQLValid(kGetStorageKeysSql));
172 sql::Statement statement(db_.GetUniqueStatement(kGetStorageKeysSql));
173 while (statement.Step()) {
174 absl::optional<blink::StorageKey> key =
175 blink::StorageKey::Deserialize(statement.ColumnString(0));
176 if (key.has_value()) {
177 storage_keys.push_back(*key);
183 bool MediaDeviceSaltDatabase::EnsureOpen(bool is_retry) {
184 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
192 db_.set_histogram_tag("Media Device Salts");
193 // base::Unretained() is safe here because `this` owns `db`.
194 db_.set_error_callback(base::BindRepeating(
195 &MediaDeviceSaltDatabase::OnDatabaseError, base::Unretained(this)));
196 if (db_path_.empty()) {
197 if (!db_.OpenInMemory()) {
200 } else if (!db_.Open(db_path_)) {
204 sql::Transaction transaction(&db_);
205 if (transaction.Begin()) {
206 sql::MetaTable metatable;
207 if (metatable.Init(&db_, kCurrentVersion, kCompatibleVersion) &&
208 metatable.GetCompatibleVersionNumber() <= kCurrentVersion &&
209 db_.Execute("CREATE TABLE IF NOT EXISTS media_device_salts("
210 " storage_key TEXT PRIMARY KEY NOT NULL,"
211 " creation_time INTEGER NOT NULL,"
212 " salt TEXT NOT NULL)") &&
213 db_.Execute("CREATE INDEX IF NOT EXISTS creation_time ON "
214 "media_device_salts(creation_time)") &&
215 transaction.Commit()) {
218 transaction.Rollback();
222 return is_retry ? false : EnsureOpen(/*is_retry=*/true);
225 void MediaDeviceSaltDatabase::OnDatabaseError(int error,
226 sql::Statement* statement) {
227 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
228 sql::UmaHistogramSqliteResult("Media.MediaDevices.SaltDatabaseErrors", error);
229 std::ignore = sql::BuiltInRecovery::RecoverIfPossible(
231 sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
232 &kMediaDeviceSaltDatabaseUseBuiltInRecoveryIfSupported);
235 } // namespace media_device_salt