Bug 1835298: Encrypt CacheAPI data on disk in PBM.r=dom-storage-reviewers,janv

Depends on D180296

Differential Revision: https://phabricator.services.mozilla.com/D180297
This commit is contained in:
hsingh 2023-10-27 14:10:08 +00:00
parent 0890b51845
commit a68c2ad072
19 changed files with 428 additions and 187 deletions

3
dom/cache/Action.h vendored
View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_cache_Action_h
#define mozilla_dom_cache_Action_h
#include "CacheCipherKeyManager.h"
#include "mozilla/Atomics.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/SafeRefPtr.h"
@ -56,7 +57,7 @@ class Action : public SafeRefCounted<Action> {
virtual void RunOnTarget(
SafeRefPtr<Resolver> aResolver,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
Data* aOptionalData) = 0;
Data* aOptionalData, const Maybe<CipherKey>& aMaybeCipherKey) = 0;
// Called on initiating thread when the Action is canceled. The Action is
// responsible for calling Resolver::Resolve() as normal; either with a

21
dom/cache/CacheCipherKeyManager.h vendored Normal file
View File

@ -0,0 +1,21 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DOM_CACHE_CACHECIPHERKEYMANAGER_H_
#define DOM_CACHE_CACHECIPHERKEYMANAGER_H_
#include "mozilla/dom/quota/CipherKeyManager.h"
#include "mozilla/dom/quota/IPCStreamCipherStrategy.h"
namespace mozilla::dom::cache {
using CipherStrategy = mozilla::dom::quota::IPCStreamCipherStrategy;
using CipherKeyManager = mozilla::dom::quota::CipherKeyManager<CipherStrategy>;
using CipherKey = CipherStrategy::KeyType;
} // namespace mozilla::dom::cache
#endif // DOM_CACHE_CACHECIPHERKEYMANAGER_H_

73
dom/cache/Context.cpp vendored
View File

@ -5,7 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/cache/Context.h"
#include "CacheCommon.h"
#include "mozilla/AutoRestore.h"
@ -19,11 +18,13 @@
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/Maybe.h"
#include "mozIStorageConnection.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "QuotaClientImpl.h"
namespace {
@ -35,8 +36,9 @@ class NullAction final : public Action {
NullAction() = default;
virtual void RunOnTarget(mozilla::SafeRefPtr<Resolver> aResolver,
const mozilla::Maybe<CacheDirectoryMetadata>&,
Data*) override {
const mozilla::Maybe<CacheDirectoryMetadata>&, Data*,
const mozilla::Maybe<mozilla::dom::cache::CipherKey>&
/* aMaybeCipherKey */) override {
// Resolve success immediately. This Action does no actual work.
MOZ_DIAGNOSTIC_ASSERT(aResolver);
aResolver->Resolve(NS_OK);
@ -216,6 +218,7 @@ class Context::QuotaInitRunnable final : public nsIRunnable {
Maybe<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<CipherKeyManager> mCipherKeyManager;
State mState;
Atomic<bool> mCanceled;
@ -399,11 +402,18 @@ Context::QuotaInitRunnable::Run() {
QM_TRY(
MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()));
QM_TRY_UNWRAP(mDirectoryMetadata->mDir,
quotaManager
->EnsureTemporaryOriginIsInitialized(
PERSISTENCE_TYPE_DEFAULT, *mDirectoryMetadata)
.map([](const auto& res) { return res.first; }));
QM_TRY_UNWRAP(
mDirectoryMetadata->mDir,
quotaManager
->EnsureTemporaryOriginIsInitialized(
mDirectoryMetadata->mPersistenceType, *mDirectoryMetadata)
.map([](const auto& res) { return res.first; }));
auto* cacheQuotaClient = CacheQuotaClient::Get();
MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
mCipherKeyManager =
cacheQuotaClient->GetOrCreateCipherKeyManager(*mDirectoryMetadata);
mState = STATE_RUN_ON_TARGET;
@ -427,7 +437,11 @@ Context::QuotaInitRunnable::Run() {
// Execute the provided initialization Action. The Action must Resolve()
// before returning.
mInitAction->RunOnTarget(resolver.clonePtr(), mDirectoryMetadata, mData);
mInitAction->RunOnTarget(
resolver.clonePtr(), mDirectoryMetadata, mData,
mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{});
MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
mData = nullptr;
@ -445,8 +459,11 @@ Context::QuotaInitRunnable::Run() {
case STATE_COMPLETING: {
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
mInitAction->CompleteOnInitiatingThread(mResult);
mContext->OnQuotaInit(mResult, mDirectoryMetadata,
mDirectoryLock.forget());
mDirectoryLock.forget(),
mCipherKeyManager.forget());
mState = STATE_COMPLETE;
// Explicitly cleanup here as the destructor could fire on any of
@ -477,12 +494,14 @@ class Context::ActionRunnable final : public nsIRunnable,
public:
ActionRunnable(SafeRefPtr<Context> aContext, Data* aData,
nsISerialEventTarget* aTarget, SafeRefPtr<Action> aAction,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata)
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
RefPtr<CipherKeyManager> aCipherKeyManager)
: mContext(std::move(aContext)),
mData(aData),
mTarget(aTarget),
mAction(std::move(aAction)),
mDirectoryMetadata(aDirectoryMetadata),
mCipherKeyManager(std::move(aCipherKeyManager)),
mInitiatingThread(GetCurrentSerialEventTarget()),
mState(STATE_INIT),
mResult(NS_OK),
@ -572,6 +591,7 @@ class Context::ActionRunnable final : public nsIRunnable,
nsCOMPtr<nsISerialEventTarget> mTarget;
SafeRefPtr<Action> mAction;
const Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
RefPtr<CipherKeyManager> mCipherKeyManager;
nsCOMPtr<nsIEventTarget> mInitiatingThread;
State mState;
nsresult mResult;
@ -633,7 +653,9 @@ Context::ActionRunnable::Run() {
mExecutingRunOnTarget = true;
mState = STATE_RUNNING;
mAction->RunOnTarget(SafeRefPtrFromThis(), mDirectoryMetadata, mData);
mAction->RunOnTarget(
SafeRefPtrFromThis(), mDirectoryMetadata, mData,
mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{});
mData = nullptr;
@ -835,6 +857,19 @@ Maybe<DirectoryLock&> Context::MaybeDirectoryLockRef() const {
return ToMaybeRef(mDirectoryLock.get());
}
CipherKeyManager& Context::MutableCipherKeyManagerRef() {
MOZ_ASSERT(mTarget->IsOnCurrentThread());
MOZ_DIAGNOSTIC_ASSERT(mCipherKeyManager);
return *mCipherKeyManager;
}
const Maybe<CacheDirectoryMetadata>& Context::MaybeCacheDirectoryMetadataRef()
const {
MOZ_ASSERT(mTarget->IsOnCurrentThread());
return mDirectoryMetadata;
}
void Context::CancelAll() {
NS_ASSERT_OWNINGTHREAD(Context);
@ -961,9 +996,9 @@ void Context::Start() {
void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) {
NS_ASSERT_OWNINGTHREAD(Context);
auto runnable =
MakeSafeRefPtr<ActionRunnable>(SafeRefPtrFromThis(), mData, mTarget,
std::move(aAction), mDirectoryMetadata);
auto runnable = MakeSafeRefPtr<ActionRunnable>(
SafeRefPtrFromThis(), mData, mTarget, std::move(aAction),
mDirectoryMetadata, mCipherKeyManager);
if (aDoomData) {
mData = nullptr;
@ -980,7 +1015,8 @@ void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) {
void Context::OnQuotaInit(
nsresult aRv, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
already_AddRefed<DirectoryLock> aDirectoryLock) {
already_AddRefed<DirectoryLock> aDirectoryLock,
already_AddRefed<CipherKeyManager> aCipherKeyManager) {
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_DIAGNOSTIC_ASSERT(mInitRunnable);
@ -988,6 +1024,11 @@ void Context::OnQuotaInit(
if (aDirectoryMetadata) {
mDirectoryMetadata.emplace(*aDirectoryMetadata);
MOZ_DIAGNOSTIC_ASSERT(!mCipherKeyManager);
mCipherKeyManager = aCipherKeyManager;
MOZ_DIAGNOSTIC_ASSERT_IF(mDirectoryMetadata->mIsPrivate, mCipherKeyManager);
}
// Always save the directory lock to ensure QuotaManager does not shutdown

9
dom/cache/Context.h vendored
View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_cache_Context_h
#define mozilla_dom_cache_Context_h
#include "CacheCipherKeyManager.h"
#include "mozilla/dom/SafeRefPtr.h"
#include "mozilla/dom/cache/Types.h"
#include "nsCOMPtr.h"
@ -125,6 +126,10 @@ class Context final : public SafeRefCounted<Context> {
Maybe<DirectoryLock&> MaybeDirectoryLockRef() const;
CipherKeyManager& MutableCipherKeyManagerRef();
const Maybe<CacheDirectoryMetadata>& MaybeCacheDirectoryMetadataRef() const;
// Cancel any Actions running or waiting to run. This should allow the
// Context to be released and Listener::RemoveContext() will be called
// when complete.
@ -178,7 +183,8 @@ class Context final : public SafeRefCounted<Context> {
void DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData = false);
void OnQuotaInit(nsresult aRv,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
already_AddRefed<DirectoryLock> aDirectoryLock);
already_AddRefed<DirectoryLock> aDirectoryLock,
already_AddRefed<CipherKeyManager> aCipherKeyManager);
SafeRefPtr<ThreadsafeHandle> CreateThreadsafeHandle();
@ -206,6 +212,7 @@ class Context final : public SafeRefCounted<Context> {
SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle;
RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<CipherKeyManager> mCipherKeyManager;
SafeRefPtr<Context> mNextContext;
public:

View File

@ -6,11 +6,11 @@
#include "mozilla/dom/cache/DBAction.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/cache/Connection.h"
#include "mozilla/dom/cache/DBSchema.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/quota/Assertions.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/net/nsFileProtocolHandler.h"
@ -21,16 +21,11 @@
#include "nsIURI.h"
#include "nsIURIMutator.h"
#include "nsIFileURL.h"
#include "nsThreadUtils.h"
namespace mozilla::dom::cache {
using mozilla::dom::quota::AssertIsOnIOThread;
using mozilla::dom::quota::Client;
using mozilla::dom::quota::CloneFileAndAppend;
using mozilla::dom::quota::IsDatabaseCorruptionError;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;
namespace {
@ -61,7 +56,7 @@ DBAction::~DBAction() = default;
void DBAction::RunOnTarget(
SafeRefPtr<Resolver> aResolver,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
Data* aOptionalData) {
Data* aOptionalData, const Maybe<CipherKey>& aMaybeCipherKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aResolver);
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
@ -89,8 +84,9 @@ void DBAction::RunOnTarget(
// If there is no previous Action, then we must open one.
if (!conn) {
QM_TRY_UNWRAP(conn, OpenConnection(*aDirectoryMetadata, *dbDir), QM_VOID,
resolveErr);
QM_TRY_UNWRAP(conn,
OpenConnection(*aDirectoryMetadata, *dbDir, aMaybeCipherKey),
QM_VOID, resolveErr);
MOZ_DIAGNOSTIC_ASSERT(conn);
// Save this connection in the shared Data object so later Actions can
@ -109,7 +105,8 @@ void DBAction::RunOnTarget(
}
Result<nsCOMPtr<mozIStorageConnection>, nsresult> DBAction::OpenConnection(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir) {
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir,
const Maybe<CipherKey>& aMaybeCipherKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= 0);
@ -124,7 +121,7 @@ Result<nsCOMPtr<mozIStorageConnection>, nsresult> DBAction::OpenConnection(
QM_TRY_INSPECT(const auto& dbFile,
CloneFileAndAppend(aDBDir, kCachesSQLiteFilename));
QM_TRY_RETURN(OpenDBConnection(aDirectoryMetadata, *dbFile));
QM_TRY_RETURN(OpenDBConnection(aDirectoryMetadata, *dbFile, aMaybeCipherKey));
}
SyncDBAction::SyncDBAction(Mode aMode) : DBAction(aMode) {}
@ -145,9 +142,11 @@ void SyncDBAction::RunWithDBOnTarget(
}
Result<nsCOMPtr<mozIStorageConnection>, nsresult> OpenDBConnection(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile) {
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile,
const Maybe<CipherKey>& aMaybeCipherKey) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= -1);
MOZ_DIAGNOSTIC_ASSERT_IF(aDirectoryMetadata.mIsPrivate, aMaybeCipherKey);
// Use our default file:// protocol handler directly to construct the database
// URL. This avoids any problems if a plugin registers a custom file://
@ -166,10 +165,22 @@ Result<nsCOMPtr<mozIStorageConnection>, nsresult> OpenDBConnection(
IntToCString(aDirectoryMetadata.mDirectoryLockId)
: EmptyCString();
const auto keyClause = [&aMaybeCipherKey] {
nsAutoCString keyClause;
if (aMaybeCipherKey) {
keyClause.AssignLiteral("&key=");
for (uint8_t byte : CipherStrategy::SerializeKey(*aMaybeCipherKey)) {
keyClause.AppendPrintf("%02x", byte);
}
}
return keyClause;
}();
nsCOMPtr<nsIFileURL> dbFileUrl;
QM_TRY(MOZ_TO_RESULT(NS_MutateURI(mutator)
.SetQuery("cache=private"_ns + directoryLockIdClause)
.Finalize(dbFileUrl)));
QM_TRY(MOZ_TO_RESULT(
NS_MutateURI(mutator)
.SetQuery("cache=private"_ns + directoryLockIdClause + keyClause)
.Finalize(dbFileUrl)));
QM_TRY_INSPECT(const auto& storageService,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,

10
dom/cache/DBAction.h vendored
View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_cache_DBAction_h
#define mozilla_dom_cache_DBAction_h
#include "CacheCipherKeyManager.h"
#include "mozilla/dom/cache/Action.h"
#include "mozilla/RefPtr.h"
#include "nsString.h"
@ -17,7 +18,8 @@ class nsIFile;
namespace mozilla::dom::cache {
Result<nsCOMPtr<mozIStorageConnection>, nsresult> OpenDBConnection(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile);
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile,
const Maybe<CipherKey>& aMaybeCipherKey);
class DBAction : public Action {
protected:
@ -41,10 +43,12 @@ class DBAction : public Action {
private:
void RunOnTarget(SafeRefPtr<Resolver> aResolver,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
Data* aOptionalData) override;
Data* aOptionalData,
const Maybe<CipherKey>& aMaybeCipherKey) override;
Result<nsCOMPtr<mozIStorageConnection>, nsresult> OpenConnection(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir);
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir,
const Maybe<CipherKey>& aMaybeCipherKey);
const Mode mMode;
};

View File

@ -6,9 +6,15 @@
#include "FileUtilsImpl.h"
#include "CacheCipherKeyManager.h"
#include "DBSchema.h"
#include "mozilla/dom/InternalResponse.h"
#include "mozilla/dom/quota/DecryptingInputStream.h"
#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
#include "mozilla/dom/quota/EncryptingOutputStream.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/ResultExtensions.h"
@ -32,11 +38,8 @@ static_assert(SNAPPY_VERSION == 0x010109);
using mozilla::dom::quota::Client;
using mozilla::dom::quota::CloneFileAndAppend;
using mozilla::dom::quota::FileInputStream;
using mozilla::dom::quota::FileOutputStream;
using mozilla::dom::quota::GetDirEntryKind;
using mozilla::dom::quota::nsIFileKind;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::QuotaObject;
@ -46,6 +49,12 @@ namespace {
// XXX This will be tweaked to something more meaningful in Bug 1383656.
const int64_t kRoundUpNumber = 20480;
// At the moment, the encrypted stream block size is assumed to be unchangeable
// between encrypting and decrypting blobs. This assumptions holds as long as we
// only encrypt in private browsing mode, but when we support encryption for
// persistent storage, this needs to be changed.
constexpr uint32_t kEncryptedStreamBlockSize = 4096;
enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP };
Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(nsIFile& aBaseDir,
@ -128,22 +137,15 @@ nsresult BodyDeleteDir(const CacheDirectoryMetadata& aDirectoryMetadata,
return NS_OK;
}
Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
Result<nsCOMPtr<nsISupports>, nsresult> BodyStartWriteStream(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aBodyId, Maybe<CipherKey> aMaybeCipherKey,
nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback) {
MOZ_DIAGNOSTIC_ASSERT(aClosure);
MOZ_DIAGNOSTIC_ASSERT(aCallback);
QM_TRY_INSPECT(const auto& idGen,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIUUIDGenerator>,
MOZ_SELECT_OVERLOAD(do_GetService),
"@mozilla.org/uuid-generator;1"));
nsID id;
QM_TRY(MOZ_TO_RESULT(idGen->GenerateUUIDInPlace(&id)));
QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, id, BODY_FILE_FINAL));
BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_FINAL));
{
QM_TRY_INSPECT(const bool& exists,
@ -153,12 +155,20 @@ Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
}
QM_TRY_INSPECT(const auto& tmpFile,
BodyIdToFile(aBaseDir, id, BODY_FILE_TMP));
BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_TMP));
QM_TRY_UNWRAP(
nsCOMPtr<nsIOutputStream> fileStream,
CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata,
Client::DOMCACHE, tmpFile.get()));
QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileStream,
CreateFileOutputStream(aDirectoryMetadata.mPersistenceType,
aDirectoryMetadata, Client::DOMCACHE,
tmpFile.get()));
const auto privateBody = aDirectoryMetadata.mIsPrivate;
if (privateBody) {
MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey);
fileStream = MakeRefPtr<quota::EncryptingOutputStream<CipherStrategy>>(
std::move(fileStream), kEncryptedStreamBlockSize, *aMaybeCipherKey);
}
const auto compressed = MakeRefPtr<SnappyCompressOutputStream>(fileStream);
@ -172,7 +182,7 @@ Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
true, // close streams
getter_AddRefs(copyContext))));
return std::make_pair(id, std::move(copyContext));
return std::move(copyContext);
}
void BodyCancelWrite(nsISupports& aCopyContext) {
@ -203,13 +213,24 @@ nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId) {
Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aId) {
const nsID& aId, Maybe<CipherKey> aMaybeCipherKey) {
QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL));
QM_TRY_RETURN(CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT,
QM_TRY_UNWRAP(nsCOMPtr<nsIInputStream> fileInputStream,
CreateFileInputStream(aDirectoryMetadata.mPersistenceType,
aDirectoryMetadata, Client::DOMCACHE,
finalFile.get()));
auto privateBody = aDirectoryMetadata.mIsPrivate;
if (privateBody) {
MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey);
fileInputStream = new quota::DecryptingInputStream<CipherStrategy>(
WrapNotNull(std::move(fileInputStream)), kEncryptedStreamBlockSize,
*aMaybeCipherKey);
}
return WrapMovingNotNull(std::move(fileInputStream));
}
nsresult BodyMaybeUpdatePaddingSize(
@ -225,7 +246,7 @@ nsresult BodyMaybeUpdatePaddingSize(
int64_t fileSize = 0;
RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, Client::DOMCACHE,
aDirectoryMetadata.mPersistenceType, aDirectoryMetadata, Client::DOMCACHE,
bodyFile.get(), -1, &fileSize);
MOZ_DIAGNOSTIC_ASSERT(quotaObject);
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
@ -256,7 +277,7 @@ nsresult BodyDeleteFiles(const CacheDirectoryMetadata& aDirectoryMetadata,
[&aDirectoryMetadata, &id](
nsIFile& bodyFile,
const nsACString& leafName) -> Result<bool, nsresult> {
nsID fileId;
nsID fileId{};
QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true,
([&aDirectoryMetadata, &bodyFile](const auto) {
DebugOnly<nsresult> result = RemoveNsIFile(

View File

@ -7,9 +7,10 @@
#ifndef mozilla_dom_cache_FileUtils_h
#define mozilla_dom_cache_FileUtils_h
#include "CacheCommon.h"
#include "CacheCipherKeyManager.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/cache/Types.h"
#include "CacheCommon.h"
#include "mozIStorageConnection.h"
#include "nsStreamUtils.h"
#include "nsTArrayForwardDeclare.h"
@ -34,8 +35,9 @@ nsresult BodyDeleteDir(const CacheDirectoryMetadata& aDirectoryMetadata,
// Returns a Result with a success value with the body id and, optionally, the
// copy context.
Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
Result<nsCOMPtr<nsISupports>, nsresult> BodyStartWriteStream(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aBodyId, Maybe<CipherKey> aMaybeCipherKey,
nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback);
void BodyCancelWrite(nsISupports& aCopyContext);
@ -44,7 +46,7 @@ nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId);
Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aId);
const nsID& aId, Maybe<CipherKey> aMaybeCipherKey);
nsresult BodyMaybeUpdatePaddingSize(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,

89
dom/cache/Manager.cpp vendored
View File

@ -7,6 +7,7 @@
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Assertions.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Mutex.h"
#include "mozilla/StaticMutex.h"
@ -30,13 +31,14 @@
#include "nsID.h"
#include "nsIFile.h"
#include "nsIThread.h"
#include "nsIUUIDGenerator.h"
#include "nsThreadUtils.h"
#include "nsTObserverArray.h"
#include "QuotaClientImpl.h"
#include "Types.h"
namespace mozilla::dom::cache {
using mozilla::dom::quota::Client;
using mozilla::dom::quota::CloneFileAndAppend;
using mozilla::dom::quota::DirectoryLock;
@ -67,6 +69,24 @@ nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
return NS_OK;
}
Maybe<CipherKey> GetOrCreateCipherKey(NotNull<Context*> aContext,
const nsID& aBodyId, bool aCreate) {
const auto& maybeMetadata = aContext->MaybeCacheDirectoryMetadataRef();
MOZ_DIAGNOSTIC_ASSERT(maybeMetadata);
auto privateOrigin = maybeMetadata->mIsPrivate;
if (!privateOrigin) {
return Nothing{};
}
nsCString bodyIdStr{aBodyId.ToString().get()};
auto& cipherKeyManager = aContext->MutableCipherKeyManagerRef();
return aCreate ? Some(cipherKeyManager.Ensure(bodyIdStr))
: cipherKeyManager.Get(bodyIdStr);
}
// An Action that is executed when a Context is first created. It ensures that
// the directory and database are setup properly. This lets other actions
// not worry about these details.
@ -173,7 +193,8 @@ class DeleteOrphanedBodyAction final : public Action {
void RunOnTarget(SafeRefPtr<Resolver> aResolver,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
Data*) override {
Data*,
const Maybe<CipherKey>& /*aMaybeCipherKey*/) override {
MOZ_DIAGNOSTIC_ASSERT(aResolver);
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
@ -635,10 +656,15 @@ class Manager::CacheMatchAction final : public Manager::BaseAction {
return NS_OK;
}
const auto& bodyId = mResponse.mBodyId;
nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream,
BodyOpen(aDirectoryMetadata, *aDBDir, mResponse.mBodyId));
QM_TRY_UNWRAP(
stream,
BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
}
mStreamList->Add(mResponse.mBodyId, std::move(stream));
@ -697,10 +723,15 @@ class Manager::CacheMatchAllAction final : public Manager::BaseAction {
continue;
}
const auto& bodyId = mSavedResponses[i].mBodyId;
nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
mSavedResponses[i].mBodyId));
QM_TRY_UNWRAP(stream,
BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(
WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
}
mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
@ -971,12 +1002,12 @@ class Manager::CachePutAllAction final : public DBAction {
struct Entry {
CacheRequest mRequest;
nsCOMPtr<nsIInputStream> mRequestStream;
nsID mRequestBodyId;
nsID mRequestBodyId{};
nsCOMPtr<nsISupports> mRequestCopyContext;
CacheResponse mResponse;
nsCOMPtr<nsIInputStream> mResponseStream;
nsID mResponseBodyId;
nsID mResponseBodyId{};
nsCOMPtr<nsISupports> mResponseCopyContext;
};
@ -1001,10 +1032,22 @@ class Manager::CachePutAllAction final : public DBAction {
if (!source) {
return NS_OK;
}
QM_TRY_INSPECT(const auto& idGen,
MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIUUIDGenerator>,
MOZ_SELECT_OVERLOAD(do_GetService),
"@mozilla.org/uuid-generator;1"));
QM_TRY_INSPECT((const auto& [bodyId, copyContext]),
BodyStartWriteStream(aDirectoryMetadata, *mDBDir, *source,
this, AsyncCopyCompleteFunc));
nsID bodyId{};
QM_TRY(MOZ_TO_RESULT(idGen->GenerateUUIDInPlace(&bodyId)));
Maybe<CipherKey> maybeKey =
GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ true);
QM_TRY_INSPECT(
const auto& copyContext,
BodyStartWriteStream(aDirectoryMetadata, *mDBDir, bodyId, maybeKey,
*source, this, AsyncCopyCompleteFunc));
if (aStreamId == RequestStream) {
aEntry.mRequestBodyId = bodyId;
@ -1218,10 +1261,15 @@ class Manager::CacheKeysAction final : public Manager::BaseAction {
continue;
}
const auto& bodyId = mSavedRequests[i].mBodyId;
nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
mSavedRequests[i].mBodyId));
QM_TRY_UNWRAP(stream,
BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(
WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
}
mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
@ -1283,10 +1331,15 @@ class Manager::StorageMatchAction final : public Manager::BaseAction {
return NS_OK;
}
const auto& bodyId = mSavedResponse.mBodyId;
nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
mSavedResponse.mBodyId));
QM_TRY_UNWRAP(
stream,
BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
}
mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
@ -1511,7 +1564,11 @@ class Manager::OpenStreamAction final : public Manager::BaseAction {
mozIStorageConnection* aConn) override {
MOZ_DIAGNOSTIC_ASSERT(aDBDir);
QM_TRY_UNWRAP(mBodyStream, BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId));
QM_TRY_UNWRAP(
mBodyStream,
BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId,
GetOrCreateCipherKey(WrapNotNull(mManager->mContext), mBodyId,
/* aCreate */ false)));
return NS_OK;
}

View File

@ -8,11 +8,13 @@
#include "DBAction.h"
#include "FileUtilsImpl.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/cache/DBSchema.h"
#include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.h"
@ -29,8 +31,7 @@ using mozilla::dom::quota::DatabaseUsageType;
using mozilla::dom::quota::GetDirEntryKind;
using mozilla::dom::quota::nsIFileKind;
using mozilla::dom::quota::OriginMetadata;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;
using mozilla::dom::quota::PrincipalMetadata;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::UsageInfo;
using mozilla::ipc::AssertIsOnBackgroundThread;
@ -123,7 +124,8 @@ Result<UsageInfo, nsresult> GetBodyUsage(nsIFile& aMorgueDir,
}
Result<int64_t, nsresult> GetPaddingSizeFromDB(
nsIFile& aDir, nsIFile& aDBFile, const OriginMetadata& aOriginMetadata) {
nsIFile& aDir, nsIFile& aDBFile, const OriginMetadata& aOriginMetadata,
const Maybe<CipherKey>& aMaybeCipherKey) {
CacheDirectoryMetadata directoryMetadata(aOriginMetadata);
// directoryMetadata.mDirectoryLockId must be -1 (which is default for new
// CacheDirectoryMetadata) because this method should only be called from
@ -142,7 +144,7 @@ Result<int64_t, nsresult> GetPaddingSizeFromDB(
#endif
QM_TRY_INSPECT(const auto& conn,
OpenDBConnection(directoryMetadata, aDBFile));
OpenDBConnection(directoryMetadata, aDBFile, aMaybeCipherKey));
// Make sure that the database has the latest schema before we try to read
// from it. We have to do this because GetPaddingSizeFromDB is called
@ -236,10 +238,19 @@ Result<UsageInfo, nsresult> CacheQuotaClient::InitOrigin(
// XXX Ensure the -wel file is removed if the caches.sqlite doesn't exist.
QM_TRY(OkIf(!!cachesSQLiteFile), UsageInfo{});
const auto maybeCipherKey = [this, &aOriginMetadata] {
Maybe<CipherKey> maybeCipherKey;
auto cipherKeyManager = GetOrCreateCipherKeyManager(aOriginMetadata);
if (cipherKeyManager) {
maybeCipherKey = Some(cipherKeyManager->Ensure());
}
return maybeCipherKey;
}();
QM_TRY_INSPECT(
const auto& paddingSize,
([dir, cachesSQLiteFile,
&aOriginMetadata]() -> Result<int64_t, nsresult> {
([dir, cachesSQLiteFile, &aOriginMetadata,
&maybeCipherKey]() -> Result<int64_t, nsresult> {
if (!DirectoryPaddingFileExists(*dir, DirPaddingFile::TMP_FILE)) {
QM_WARNONLY_TRY_UNWRAP(const auto maybePaddingSize,
DirectoryPaddingGet(*dir));
@ -251,8 +262,8 @@ Result<UsageInfo, nsresult> CacheQuotaClient::InitOrigin(
// If the temporary file still exists or failing to get the padding size
// from the padding file, then we need to get the padding size from the
// database and restore the padding file.
QM_TRY_RETURN(
GetPaddingSizeFromDB(*dir, *cachesSQLiteFile, aOriginMetadata));
QM_TRY_RETURN(GetPaddingSizeFromDB(*dir, *cachesSQLiteFile,
aOriginMetadata, maybeCipherKey));
}()));
QM_TRY_INSPECT(
@ -343,13 +354,20 @@ Result<UsageInfo, nsresult> CacheQuotaClient::GetUsageForOrigin(
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
return quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT,
return quotaManager->GetUsageForClient(aOriginMetadata.mPersistenceType,
aOriginMetadata, Client::DOMCACHE);
}
void CacheQuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) {
// Nothing to do here.
AssertIsOnIOThread();
if (aPersistenceType == quota::PERSISTENCE_TYPE_PRIVATE) {
if (auto entry = mCipherKeyManagers.Lookup(aOrigin)) {
entry.Data()->Invalidate();
entry.Remove();
}
}
}
void CacheQuotaClient::OnRepositoryClearCompleted(
@ -492,6 +510,20 @@ nsresult CacheQuotaClient::WipePaddingFileInternal(
return NS_OK;
}
RefPtr<CipherKeyManager> CacheQuotaClient::GetOrCreateCipherKeyManager(
const PrincipalMetadata& aMetadata) {
AssertIsOnIOThread();
auto privateOrigin = aMetadata.mIsPrivate;
if (!privateOrigin) {
return nullptr;
}
const auto& origin = aMetadata.mOrigin;
return mCipherKeyManagers.LookupOrInsertWith(
origin, [] { return new CipherKeyManager("CacheCipherKeyManager"); });
}
CacheQuotaClient::~CacheQuotaClient() {
AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(sInstance == this);

View File

@ -7,9 +7,12 @@
#ifndef mozilla_dom_cache_QuotaClientImpl_h
#define mozilla_dom_cache_QuotaClientImpl_h
#include "CacheCipherKeyManager.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/QMResult.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/quota/ResultExtensions.h"
namespace mozilla::dom::cache {
@ -119,6 +122,9 @@ class CacheQuotaClient final : public quota::Client {
nsresult WipePaddingFileInternal(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aBaseDir);
RefPtr<CipherKeyManager> GetOrCreateCipherKeyManager(
const quota::PrincipalMetadata& aMetadata);
private:
~CacheQuotaClient();
@ -128,6 +134,9 @@ class CacheQuotaClient final : public quota::Client {
void ForceKillActors() override;
void FinalizeShutdown() override;
// Should always be accessed from QM IO thread.
nsTHashMap<nsCStringHashKey, RefPtr<CipherKeyManager>> mCipherKeyManagers;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheQuotaClient, override)
};

14
dom/cache/Types.h vendored
View File

@ -10,6 +10,7 @@
#include <functional>
#include <stdint.h>
#include "mozilla/dom/quota/CommonMetadata.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsIInputStream.h"
@ -31,17 +32,20 @@ struct CacheDirectoryMetadata : quota::ClientMetadata {
nsCOMPtr<nsIFile> mDir;
int64_t mDirectoryLockId = -1;
explicit CacheDirectoryMetadata(quota::PrincipalMetadata aPrincipalMetadata)
explicit CacheDirectoryMetadata(
const quota::PrincipalMetadata& aPrincipalMetadata)
: quota::ClientMetadata(
quota::OriginMetadata{std::move(aPrincipalMetadata),
quota::PERSISTENCE_TYPE_DEFAULT},
quota::OriginMetadata{aPrincipalMetadata,
aPrincipalMetadata.mIsPrivate
? quota::PERSISTENCE_TYPE_PRIVATE
: quota::PERSISTENCE_TYPE_DEFAULT},
quota::Client::Type::DOMCACHE) {}
explicit CacheDirectoryMetadata(quota::OriginMetadata aOriginMetadata)
: quota::ClientMetadata(std::move(aOriginMetadata),
quota::Client::Type::DOMCACHE) {
MOZ_DIAGNOSTIC_ASSERT(aOriginMetadata.mPersistenceType ==
quota::PERSISTENCE_TYPE_DEFAULT);
MOZ_DIAGNOSTIC_ASSERT(mPersistenceType == quota::PERSISTENCE_TYPE_DEFAULT ||
mPersistenceType == quota::PERSISTENCE_TYPE_PRIVATE);
}
};

View File

@ -11605,6 +11605,10 @@ DatabaseFileManager::DatabaseFileManager(
mOriginMetadata(aOriginMetadata),
mDatabaseName(aDatabaseName),
mDatabaseID(aDatabaseID),
mCipherKeyManager(
aIsInPrivateBrowsingMode
? new IndexedDBCipherKeyManager("IndexedDBCipherKeyManager")
: nullptr),
mEnforcingQuota(aEnforcingQuota),
mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {}
@ -11963,7 +11967,9 @@ nsresult DatabaseFileManager::SyncDeleteFile(nsIFile& aFile,
}
nsresult DatabaseFileManager::Invalidate() {
mCipherKeyManager.Invalidate();
if (mCipherKeyManager) {
mCipherKeyManager->Invalidate();
}
QM_TRY(MOZ_TO_RESULT(FileInfoManager::Invalidate()));

View File

@ -32,7 +32,7 @@ class DatabaseFileManager final
const nsString mDatabaseName;
const nsCString mDatabaseID;
mutable IndexedDBCipherKeyManager mCipherKeyManager;
RefPtr<IndexedDBCipherKeyManager> mCipherKeyManager;
LazyInitializedOnce<const nsString> mDirectoryPath;
LazyInitializedOnce<const nsString> mJournalDirectoryPath;
@ -84,7 +84,10 @@ class DatabaseFileManager final
const nsCString& DatabaseID() const { return mDatabaseID; }
IndexedDBCipherKeyManager& MutableCipherKeyManagerRef() const {
return mCipherKeyManager;
MOZ_ASSERT(mIsInPrivateBrowsingMode);
MOZ_ASSERT(mCipherKeyManager);
return *mCipherKeyManager;
}
auto IsInPrivateBrowsingMode() const { return mIsInPrivateBrowsingMode; }

View File

@ -1,53 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "IndexedDBCipherKeyManager.h"
#include "mozilla/dom/quota/QuotaCommon.h"
namespace mozilla::dom::indexedDB {
Maybe<CipherKey> IndexedDBCipherKeyManager::Get(const nsACString& aKeyId) {
auto lockedCipherKeys = mCipherKeys.Lock();
MOZ_ASSERT(!mInvalidated);
return lockedCipherKeys->MaybeGet(aKeyId);
}
CipherKey IndexedDBCipherKeyManager::Ensure(const nsACString& aKeyId) {
auto lockedCipherKeys = mCipherKeys.Lock();
MOZ_ASSERT(!mInvalidated);
return lockedCipherKeys->LookupOrInsertWith(aKeyId, [] {
// Generate a new key if one corresponding to keyStoreId does not exist
// already.
QM_TRY_RETURN(IndexedDBCipherStrategy::GenerateKey(), [](const auto&) {
// Bug1800110 Propagate the error to the caller rather than asserting.
MOZ_RELEASE_ASSERT(false);
return CipherKey{};
});
});
}
bool IndexedDBCipherKeyManager::Invalidated() {
auto lockedCipherKeys = mCipherKeys.Lock();
return mInvalidated;
}
void IndexedDBCipherKeyManager::Invalidate() {
auto lockedCipherKeys = mCipherKeys.Lock();
mInvalidated.Flip();
lockedCipherKeys->Clear();
}
} // namespace mozilla::dom::indexedDB

View File

@ -7,47 +7,25 @@
#ifndef DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_
#define DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_
#include "FlippedOnce.h"
#include "mozilla/DataMutex.h"
#include "mozilla/dom/quota/CipherKeyManager.h"
#include "mozilla/dom/quota/IPCStreamCipherStrategy.h"
#include "nsTHashMap.h"
namespace mozilla::dom::indexedDB {
namespace mozilla::dom {
using IndexedDBCipherStrategy = quota::IPCStreamCipherStrategy;
// IndexedDBCipherKeyManager is used by IndexedDB operations to store/retrieve
// keys in private browsing mode. All data in IndexedDB must be encrypted
// using a cipher key and unique IV (Initialization Vector). While there's a
// separate cipher key for every blob file; the SQLite database gets encrypted
// using the commmon database key. All keys pertaining to a single IndexedDB
// database get stored together using quota::CipherKeyManager. So, the hashmap
// can be used to look up the common database key and blob keys using "default"
// and blob file ids respectively.
using IndexedDBCipherStrategy = mozilla::dom::quota::IPCStreamCipherStrategy;
using IndexedDBCipherKeyManager =
mozilla::dom::quota::CipherKeyManager<IndexedDBCipherStrategy>;
using CipherKey = IndexedDBCipherStrategy::KeyType;
class IndexedDBCipherKeyManager {
// This helper class is used by IndexedDB operations to store/retrieve cipher
// keys in private browsing mode. All data in IndexedDB must be encrypted
// using a cipher key and unique IV (Initialization Vector). While there's a
// separate cipher key for every blob file; the SQLite database gets encrypted
// using the commmon database key. All keys pertaining to a single IndexedDB
// database get stored together in a hashmap. So the hashmap can be used to
// to look up the common database key and blob keys using "default" and blob
// file ids respectively.
public:
IndexedDBCipherKeyManager() : mCipherKeys("IndexedDBCipherKeyManager"){};
Maybe<CipherKey> Get(const nsACString& aKeyId = "default"_ns);
CipherKey Ensure(const nsACString& aKeyId = "default"_ns);
bool Invalidated();
// After calling this method, callers should not call any more methods on this
// class.
void Invalidate();
private:
// XXX Maybe we can avoid a mutex here by moving all accesses to the
// background thread.
DataMutex<nsTHashMap<nsCStringHashKey, CipherKey>> mCipherKeys;
FlippedOnce<false> mInvalidated;
};
} // namespace mozilla::dom::indexedDB
} // namespace mozilla::dom
#endif // DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_

View File

@ -69,7 +69,6 @@ UNIFIED_SOURCES += [
"IDBTransaction.cpp",
"IndexedDatabase.cpp",
"IndexedDatabaseManager.cpp",
"IndexedDBCipherKeyManager.cpp",
"IndexedDBCommon.cpp",
"KeyPath.cpp",
"ProfilerHelpers.cpp",

View File

@ -0,0 +1,97 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DOM_QUOTA_CIPHERKEYMANAGER_H_
#define DOM_QUOTA_CIPHERKEYMANAGER_H_
#include "mozilla/DataMutex.h"
#include "mozilla/dom/FlippedOnce.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "nsTHashMap.h"
namespace mozilla::dom::quota {
using mozilla::FlippedOnce;
template <typename CipherStrategy>
class CipherKeyManager {
// This helper class is used by quota clients to store/retrieve cipher
// keys in private browsing mode. All data in private mode must be encrypted
// using a cipher key and unique IV (Initialization Vector).
// This class uses hashmap (represented by mCipherKeys) to store cipher keys
// and is currently used by IndexedDB and Cache quota clients. At any given
// time, IndexedDB may contain multiple instances of this class where each is
// used to cipherkeys relevant to a particular database. Unlike IndexedDB,
// CacheAPI only has one physical sqlite db per origin, so all cipher keys
// corresponding to an origin in cacheAPI gets stored together in this
// hashmap.
// Bug1859558: It could be better if QuotaManager owns cipherKeys for
// all the quota clients and exposes, methods like
// GetOrCreateCipherManager(aOrigin, aDatabaseName, aClientType) for
// clients to access their respective cipherKeys scoped.
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CipherKeyManager)
using CipherKey = typename CipherStrategy::KeyType;
public:
explicit CipherKeyManager(const char* aName) : mCipherKeys(aName){};
Maybe<CipherKey> Get(const nsACString& aKeyId = "default"_ns) {
auto lockedCipherKeys = mCipherKeys.Lock();
MOZ_ASSERT(!mInvalidated);
return lockedCipherKeys->MaybeGet(aKeyId);
}
CipherKey Ensure(const nsACString& aKeyId = "default"_ns) {
auto lockedCipherKeys = mCipherKeys.Lock();
MOZ_ASSERT(!mInvalidated);
return lockedCipherKeys->LookupOrInsertWith(aKeyId, [] {
// Generate a new key if one corresponding to keyStoreId does not exist
// already.
QM_TRY_RETURN(CipherStrategy::GenerateKey(), [](const auto&) {
// Bug1800110 Propagate the error to the caller rather than asserting.
MOZ_RELEASE_ASSERT(false);
return CipherKey{};
});
});
}
bool Invalidated() {
auto lockedCipherKeys = mCipherKeys.Lock();
return mInvalidated;
}
// After calling this method, callers should not call any more methods on this
// class.
void Invalidate() {
auto lockedCipherKeys = mCipherKeys.Lock();
mInvalidated.Flip();
lockedCipherKeys->Clear();
}
private:
~CipherKeyManager() = default;
// XXX Maybe we can avoid a mutex here by moving all accesses to the
// background thread.
DataMutex<nsTHashMap<nsCStringHashKey, CipherKey>> mCipherKeys;
FlippedOnce<false> mInvalidated;
};
} // namespace mozilla::dom::quota
#endif // DOM_QUOTA_CIPHERKEYMANAGER_H_

View File

@ -29,6 +29,7 @@ EXPORTS.mozilla.dom.quota += [
"AssertionsImpl.h",
"CachingDatabaseConnection.h",
"CheckedUnsafePtr.h",
"CipherKeyManager.h",
"CipherStrategy.h",
"Client.h",
"ClientImpl.h",