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 #ifndef mozilla_dom_cache_Action_h
#define mozilla_dom_cache_Action_h #define mozilla_dom_cache_Action_h
#include "CacheCipherKeyManager.h"
#include "mozilla/Atomics.h" #include "mozilla/Atomics.h"
#include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/SafeRefPtr.h" #include "mozilla/dom/SafeRefPtr.h"
@ -56,7 +57,7 @@ class Action : public SafeRefCounted<Action> {
virtual void RunOnTarget( virtual void RunOnTarget(
SafeRefPtr<Resolver> aResolver, SafeRefPtr<Resolver> aResolver,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, 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 // Called on initiating thread when the Action is canceled. The Action is
// responsible for calling Resolver::Resolve() as normal; either with a // 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/cache/Context.h" #include "mozilla/dom/cache/Context.h"
#include "CacheCommon.h" #include "CacheCommon.h"
#include "mozilla/AutoRestore.h" #include "mozilla/AutoRestore.h"
@ -19,11 +18,13 @@
#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h" #include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h" #include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/Maybe.h"
#include "mozIStorageConnection.h" #include "mozIStorageConnection.h"
#include "nsIPrincipal.h" #include "nsIPrincipal.h"
#include "nsIRunnable.h" #include "nsIRunnable.h"
#include "nsIThread.h" #include "nsIThread.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "QuotaClientImpl.h"
namespace { namespace {
@ -35,8 +36,9 @@ class NullAction final : public Action {
NullAction() = default; NullAction() = default;
virtual void RunOnTarget(mozilla::SafeRefPtr<Resolver> aResolver, virtual void RunOnTarget(mozilla::SafeRefPtr<Resolver> aResolver,
const mozilla::Maybe<CacheDirectoryMetadata>&, const mozilla::Maybe<CacheDirectoryMetadata>&, Data*,
Data*) override { const mozilla::Maybe<mozilla::dom::cache::CipherKey>&
/* aMaybeCipherKey */) override {
// Resolve success immediately. This Action does no actual work. // Resolve success immediately. This Action does no actual work.
MOZ_DIAGNOSTIC_ASSERT(aResolver); MOZ_DIAGNOSTIC_ASSERT(aResolver);
aResolver->Resolve(NS_OK); aResolver->Resolve(NS_OK);
@ -216,6 +218,7 @@ class Context::QuotaInitRunnable final : public nsIRunnable {
Maybe<mozilla::ipc::PrincipalInfo> mPrincipalInfo; Maybe<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
Maybe<CacheDirectoryMetadata> mDirectoryMetadata; Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
RefPtr<DirectoryLock> mDirectoryLock; RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<CipherKeyManager> mCipherKeyManager;
State mState; State mState;
Atomic<bool> mCanceled; Atomic<bool> mCanceled;
@ -399,11 +402,18 @@ Context::QuotaInitRunnable::Run() {
QM_TRY( QM_TRY(
MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized())); MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()));
QM_TRY_UNWRAP(mDirectoryMetadata->mDir, QM_TRY_UNWRAP(
quotaManager mDirectoryMetadata->mDir,
->EnsureTemporaryOriginIsInitialized( quotaManager
PERSISTENCE_TYPE_DEFAULT, *mDirectoryMetadata) ->EnsureTemporaryOriginIsInitialized(
.map([](const auto& res) { return res.first; })); 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; mState = STATE_RUN_ON_TARGET;
@ -427,7 +437,11 @@ Context::QuotaInitRunnable::Run() {
// Execute the provided initialization Action. The Action must Resolve() // Execute the provided initialization Action. The Action must Resolve()
// before returning. // before returning.
mInitAction->RunOnTarget(resolver.clonePtr(), mDirectoryMetadata, mData);
mInitAction->RunOnTarget(
resolver.clonePtr(), mDirectoryMetadata, mData,
mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{});
MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved()); MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
mData = nullptr; mData = nullptr;
@ -445,8 +459,11 @@ Context::QuotaInitRunnable::Run() {
case STATE_COMPLETING: { case STATE_COMPLETING: {
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable); NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
mInitAction->CompleteOnInitiatingThread(mResult); mInitAction->CompleteOnInitiatingThread(mResult);
mContext->OnQuotaInit(mResult, mDirectoryMetadata, mContext->OnQuotaInit(mResult, mDirectoryMetadata,
mDirectoryLock.forget()); mDirectoryLock.forget(),
mCipherKeyManager.forget());
mState = STATE_COMPLETE; mState = STATE_COMPLETE;
// Explicitly cleanup here as the destructor could fire on any of // Explicitly cleanup here as the destructor could fire on any of
@ -477,12 +494,14 @@ class Context::ActionRunnable final : public nsIRunnable,
public: public:
ActionRunnable(SafeRefPtr<Context> aContext, Data* aData, ActionRunnable(SafeRefPtr<Context> aContext, Data* aData,
nsISerialEventTarget* aTarget, SafeRefPtr<Action> aAction, nsISerialEventTarget* aTarget, SafeRefPtr<Action> aAction,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata) const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
RefPtr<CipherKeyManager> aCipherKeyManager)
: mContext(std::move(aContext)), : mContext(std::move(aContext)),
mData(aData), mData(aData),
mTarget(aTarget), mTarget(aTarget),
mAction(std::move(aAction)), mAction(std::move(aAction)),
mDirectoryMetadata(aDirectoryMetadata), mDirectoryMetadata(aDirectoryMetadata),
mCipherKeyManager(std::move(aCipherKeyManager)),
mInitiatingThread(GetCurrentSerialEventTarget()), mInitiatingThread(GetCurrentSerialEventTarget()),
mState(STATE_INIT), mState(STATE_INIT),
mResult(NS_OK), mResult(NS_OK),
@ -572,6 +591,7 @@ class Context::ActionRunnable final : public nsIRunnable,
nsCOMPtr<nsISerialEventTarget> mTarget; nsCOMPtr<nsISerialEventTarget> mTarget;
SafeRefPtr<Action> mAction; SafeRefPtr<Action> mAction;
const Maybe<CacheDirectoryMetadata> mDirectoryMetadata; const Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
RefPtr<CipherKeyManager> mCipherKeyManager;
nsCOMPtr<nsIEventTarget> mInitiatingThread; nsCOMPtr<nsIEventTarget> mInitiatingThread;
State mState; State mState;
nsresult mResult; nsresult mResult;
@ -633,7 +653,9 @@ Context::ActionRunnable::Run() {
mExecutingRunOnTarget = true; mExecutingRunOnTarget = true;
mState = STATE_RUNNING; mState = STATE_RUNNING;
mAction->RunOnTarget(SafeRefPtrFromThis(), mDirectoryMetadata, mData); mAction->RunOnTarget(
SafeRefPtrFromThis(), mDirectoryMetadata, mData,
mCipherKeyManager ? Some(mCipherKeyManager->Ensure()) : Nothing{});
mData = nullptr; mData = nullptr;
@ -835,6 +857,19 @@ Maybe<DirectoryLock&> Context::MaybeDirectoryLockRef() const {
return ToMaybeRef(mDirectoryLock.get()); 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() { void Context::CancelAll() {
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
@ -961,9 +996,9 @@ void Context::Start() {
void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) { void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) {
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
auto runnable = auto runnable = MakeSafeRefPtr<ActionRunnable>(
MakeSafeRefPtr<ActionRunnable>(SafeRefPtrFromThis(), mData, mTarget, SafeRefPtrFromThis(), mData, mTarget, std::move(aAction),
std::move(aAction), mDirectoryMetadata); mDirectoryMetadata, mCipherKeyManager);
if (aDoomData) { if (aDoomData) {
mData = nullptr; mData = nullptr;
@ -980,7 +1015,8 @@ void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) {
void Context::OnQuotaInit( void Context::OnQuotaInit(
nsresult aRv, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, nsresult aRv, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
already_AddRefed<DirectoryLock> aDirectoryLock) { already_AddRefed<DirectoryLock> aDirectoryLock,
already_AddRefed<CipherKeyManager> aCipherKeyManager) {
NS_ASSERT_OWNINGTHREAD(Context); NS_ASSERT_OWNINGTHREAD(Context);
MOZ_DIAGNOSTIC_ASSERT(mInitRunnable); MOZ_DIAGNOSTIC_ASSERT(mInitRunnable);
@ -988,6 +1024,11 @@ void Context::OnQuotaInit(
if (aDirectoryMetadata) { if (aDirectoryMetadata) {
mDirectoryMetadata.emplace(*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 // 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 #ifndef mozilla_dom_cache_Context_h
#define mozilla_dom_cache_Context_h #define mozilla_dom_cache_Context_h
#include "CacheCipherKeyManager.h"
#include "mozilla/dom/SafeRefPtr.h" #include "mozilla/dom/SafeRefPtr.h"
#include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/Types.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
@ -125,6 +126,10 @@ class Context final : public SafeRefCounted<Context> {
Maybe<DirectoryLock&> MaybeDirectoryLockRef() const; Maybe<DirectoryLock&> MaybeDirectoryLockRef() const;
CipherKeyManager& MutableCipherKeyManagerRef();
const Maybe<CacheDirectoryMetadata>& MaybeCacheDirectoryMetadataRef() const;
// Cancel any Actions running or waiting to run. This should allow the // Cancel any Actions running or waiting to run. This should allow the
// Context to be released and Listener::RemoveContext() will be called // Context to be released and Listener::RemoveContext() will be called
// when complete. // when complete.
@ -178,7 +183,8 @@ class Context final : public SafeRefCounted<Context> {
void DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData = false); void DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData = false);
void OnQuotaInit(nsresult aRv, void OnQuotaInit(nsresult aRv,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
already_AddRefed<DirectoryLock> aDirectoryLock); already_AddRefed<DirectoryLock> aDirectoryLock,
already_AddRefed<CipherKeyManager> aCipherKeyManager);
SafeRefPtr<ThreadsafeHandle> CreateThreadsafeHandle(); SafeRefPtr<ThreadsafeHandle> CreateThreadsafeHandle();
@ -206,6 +212,7 @@ class Context final : public SafeRefCounted<Context> {
SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle; SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle;
RefPtr<DirectoryLock> mDirectoryLock; RefPtr<DirectoryLock> mDirectoryLock;
RefPtr<CipherKeyManager> mCipherKeyManager;
SafeRefPtr<Context> mNextContext; SafeRefPtr<Context> mNextContext;
public: public:

View file

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

View file

@ -6,9 +6,15 @@
#include "FileUtilsImpl.h" #include "FileUtilsImpl.h"
#include "CacheCipherKeyManager.h"
#include "DBSchema.h" #include "DBSchema.h"
#include "mozilla/dom/InternalResponse.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/FileStreams.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h" #include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/ResultExtensions.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::Client;
using mozilla::dom::quota::CloneFileAndAppend; using mozilla::dom::quota::CloneFileAndAppend;
using mozilla::dom::quota::FileInputStream;
using mozilla::dom::quota::FileOutputStream;
using mozilla::dom::quota::GetDirEntryKind; using mozilla::dom::quota::GetDirEntryKind;
using mozilla::dom::quota::nsIFileKind; using mozilla::dom::quota::nsIFileKind;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::QuotaObject; using mozilla::dom::quota::QuotaObject;
@ -46,6 +49,12 @@ namespace {
// XXX This will be tweaked to something more meaningful in Bug 1383656. // XXX This will be tweaked to something more meaningful in Bug 1383656.
const int64_t kRoundUpNumber = 20480; 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 }; enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP };
Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(nsIFile& aBaseDir, Result<NotNull<nsCOMPtr<nsIFile>>, nsresult> BodyIdToFile(nsIFile& aBaseDir,
@ -128,22 +137,15 @@ nsresult BodyDeleteDir(const CacheDirectoryMetadata& aDirectoryMetadata,
return NS_OK; return NS_OK;
} }
Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream( Result<nsCOMPtr<nsISupports>, nsresult> BodyStartWriteStream(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aBodyId, Maybe<CipherKey> aMaybeCipherKey,
nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback) { nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback) {
MOZ_DIAGNOSTIC_ASSERT(aClosure); MOZ_DIAGNOSTIC_ASSERT(aClosure);
MOZ_DIAGNOSTIC_ASSERT(aCallback); 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, QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, id, BODY_FILE_FINAL)); BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_FINAL));
{ {
QM_TRY_INSPECT(const bool& exists, QM_TRY_INSPECT(const bool& exists,
@ -153,12 +155,20 @@ Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
} }
QM_TRY_INSPECT(const auto& tmpFile, QM_TRY_INSPECT(const auto& tmpFile,
BodyIdToFile(aBaseDir, id, BODY_FILE_TMP)); BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_TMP));
QM_TRY_UNWRAP( QM_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileStream,
nsCOMPtr<nsIOutputStream> fileStream, CreateFileOutputStream(aDirectoryMetadata.mPersistenceType,
CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, aDirectoryMetadata, Client::DOMCACHE,
Client::DOMCACHE, tmpFile.get())); 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); const auto compressed = MakeRefPtr<SnappyCompressOutputStream>(fileStream);
@ -172,7 +182,7 @@ Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream(
true, // close streams true, // close streams
getter_AddRefs(copyContext)))); getter_AddRefs(copyContext))));
return std::make_pair(id, std::move(copyContext)); return std::move(copyContext);
} }
void BodyCancelWrite(nsISupports& aCopyContext) { void BodyCancelWrite(nsISupports& aCopyContext) {
@ -203,13 +213,24 @@ nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId) {
Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen( Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aId) { const nsID& aId, Maybe<CipherKey> aMaybeCipherKey) {
QM_TRY_INSPECT(const auto& finalFile, QM_TRY_INSPECT(const auto& finalFile,
BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL)); 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, aDirectoryMetadata, Client::DOMCACHE,
finalFile.get())); 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( nsresult BodyMaybeUpdatePaddingSize(
@ -225,7 +246,7 @@ nsresult BodyMaybeUpdatePaddingSize(
int64_t fileSize = 0; int64_t fileSize = 0;
RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject( RefPtr<QuotaObject> quotaObject = quotaManager->GetQuotaObject(
PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, Client::DOMCACHE, aDirectoryMetadata.mPersistenceType, aDirectoryMetadata, Client::DOMCACHE,
bodyFile.get(), -1, &fileSize); bodyFile.get(), -1, &fileSize);
MOZ_DIAGNOSTIC_ASSERT(quotaObject); MOZ_DIAGNOSTIC_ASSERT(quotaObject);
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0); MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
@ -256,7 +277,7 @@ nsresult BodyDeleteFiles(const CacheDirectoryMetadata& aDirectoryMetadata,
[&aDirectoryMetadata, &id]( [&aDirectoryMetadata, &id](
nsIFile& bodyFile, nsIFile& bodyFile,
const nsACString& leafName) -> Result<bool, nsresult> { const nsACString& leafName) -> Result<bool, nsresult> {
nsID fileId; nsID fileId{};
QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true, QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true,
([&aDirectoryMetadata, &bodyFile](const auto) { ([&aDirectoryMetadata, &bodyFile](const auto) {
DebugOnly<nsresult> result = RemoveNsIFile( DebugOnly<nsresult> result = RemoveNsIFile(

View file

@ -7,9 +7,10 @@
#ifndef mozilla_dom_cache_FileUtils_h #ifndef mozilla_dom_cache_FileUtils_h
#define mozilla_dom_cache_FileUtils_h #define mozilla_dom_cache_FileUtils_h
#include "CacheCommon.h"
#include "CacheCipherKeyManager.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/Types.h"
#include "CacheCommon.h"
#include "mozIStorageConnection.h" #include "mozIStorageConnection.h"
#include "nsStreamUtils.h" #include "nsStreamUtils.h"
#include "nsTArrayForwardDeclare.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 // Returns a Result with a success value with the body id and, optionally, the
// copy context. // copy context.
Result<std::pair<nsID, nsCOMPtr<nsISupports>>, nsresult> BodyStartWriteStream( Result<nsCOMPtr<nsISupports>, nsresult> BodyStartWriteStream(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aBodyId, Maybe<CipherKey> aMaybeCipherKey,
nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback); nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback);
void BodyCancelWrite(nsISupports& aCopyContext); void BodyCancelWrite(nsISupports& aCopyContext);
@ -44,7 +46,7 @@ nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId);
Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen( Result<MovingNotNull<nsCOMPtr<nsIInputStream>>, nsresult> BodyOpen(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir,
const nsID& aId); const nsID& aId, Maybe<CipherKey> aMaybeCipherKey);
nsresult BodyMaybeUpdatePaddingSize( nsresult BodyMaybeUpdatePaddingSize(
const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, 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/dom/cache/Manager.h"
#include "mozilla/AppShutdown.h" #include "mozilla/AppShutdown.h"
#include "mozilla/Assertions.h"
#include "mozilla/AutoRestore.h" #include "mozilla/AutoRestore.h"
#include "mozilla/Mutex.h" #include "mozilla/Mutex.h"
#include "mozilla/StaticMutex.h" #include "mozilla/StaticMutex.h"
@ -30,13 +31,14 @@
#include "nsID.h" #include "nsID.h"
#include "nsIFile.h" #include "nsIFile.h"
#include "nsIThread.h" #include "nsIThread.h"
#include "nsIUUIDGenerator.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "nsTObserverArray.h" #include "nsTObserverArray.h"
#include "QuotaClientImpl.h" #include "QuotaClientImpl.h"
#include "Types.h"
namespace mozilla::dom::cache { namespace mozilla::dom::cache {
using mozilla::dom::quota::Client;
using mozilla::dom::quota::CloneFileAndAppend; using mozilla::dom::quota::CloneFileAndAppend;
using mozilla::dom::quota::DirectoryLock; using mozilla::dom::quota::DirectoryLock;
@ -67,6 +69,24 @@ nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
return NS_OK; 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 // 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 // the directory and database are setup properly. This lets other actions
// not worry about these details. // not worry about these details.
@ -173,7 +193,8 @@ class DeleteOrphanedBodyAction final : public Action {
void RunOnTarget(SafeRefPtr<Resolver> aResolver, void RunOnTarget(SafeRefPtr<Resolver> aResolver,
const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
Data*) override { Data*,
const Maybe<CipherKey>& /*aMaybeCipherKey*/) override {
MOZ_DIAGNOSTIC_ASSERT(aResolver); MOZ_DIAGNOSTIC_ASSERT(aResolver);
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata); MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir); MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
@ -635,10 +656,15 @@ class Manager::CacheMatchAction final : public Manager::BaseAction {
return NS_OK; return NS_OK;
} }
const auto& bodyId = mResponse.mBodyId;
nsCOMPtr<nsIInputStream> stream; nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) { if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, QM_TRY_UNWRAP(
BodyOpen(aDirectoryMetadata, *aDBDir, mResponse.mBodyId)); stream,
BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
} }
mStreamList->Add(mResponse.mBodyId, std::move(stream)); mStreamList->Add(mResponse.mBodyId, std::move(stream));
@ -697,10 +723,15 @@ class Manager::CacheMatchAllAction final : public Manager::BaseAction {
continue; continue;
} }
const auto& bodyId = mSavedResponses[i].mBodyId;
nsCOMPtr<nsIInputStream> stream; nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) { if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir, QM_TRY_UNWRAP(stream,
mSavedResponses[i].mBodyId)); BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(
WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
} }
mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream)); mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
@ -971,12 +1002,12 @@ class Manager::CachePutAllAction final : public DBAction {
struct Entry { struct Entry {
CacheRequest mRequest; CacheRequest mRequest;
nsCOMPtr<nsIInputStream> mRequestStream; nsCOMPtr<nsIInputStream> mRequestStream;
nsID mRequestBodyId; nsID mRequestBodyId{};
nsCOMPtr<nsISupports> mRequestCopyContext; nsCOMPtr<nsISupports> mRequestCopyContext;
CacheResponse mResponse; CacheResponse mResponse;
nsCOMPtr<nsIInputStream> mResponseStream; nsCOMPtr<nsIInputStream> mResponseStream;
nsID mResponseBodyId; nsID mResponseBodyId{};
nsCOMPtr<nsISupports> mResponseCopyContext; nsCOMPtr<nsISupports> mResponseCopyContext;
}; };
@ -1001,10 +1032,22 @@ class Manager::CachePutAllAction final : public DBAction {
if (!source) { if (!source) {
return NS_OK; 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]), nsID bodyId{};
BodyStartWriteStream(aDirectoryMetadata, *mDBDir, *source, QM_TRY(MOZ_TO_RESULT(idGen->GenerateUUIDInPlace(&bodyId)));
this, AsyncCopyCompleteFunc));
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) { if (aStreamId == RequestStream) {
aEntry.mRequestBodyId = bodyId; aEntry.mRequestBodyId = bodyId;
@ -1218,10 +1261,15 @@ class Manager::CacheKeysAction final : public Manager::BaseAction {
continue; continue;
} }
const auto& bodyId = mSavedRequests[i].mBodyId;
nsCOMPtr<nsIInputStream> stream; nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) { if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir, QM_TRY_UNWRAP(stream,
mSavedRequests[i].mBodyId)); BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(
WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
} }
mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream)); mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
@ -1283,10 +1331,15 @@ class Manager::StorageMatchAction final : public Manager::BaseAction {
return NS_OK; return NS_OK;
} }
const auto& bodyId = mSavedResponse.mBodyId;
nsCOMPtr<nsIInputStream> stream; nsCOMPtr<nsIInputStream> stream;
if (mArgs.openMode() == OpenMode::Eager) { if (mArgs.openMode() == OpenMode::Eager) {
QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir, QM_TRY_UNWRAP(
mSavedResponse.mBodyId)); stream,
BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
/* aCreate */ false)));
} }
mStreamList->Add(mSavedResponse.mBodyId, std::move(stream)); mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
@ -1511,7 +1564,11 @@ class Manager::OpenStreamAction final : public Manager::BaseAction {
mozIStorageConnection* aConn) override { mozIStorageConnection* aConn) override {
MOZ_DIAGNOSTIC_ASSERT(aDBDir); 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; return NS_OK;
} }

View file

@ -8,11 +8,13 @@
#include "DBAction.h" #include "DBAction.h"
#include "FileUtilsImpl.h" #include "FileUtilsImpl.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ResultExtensions.h" #include "mozilla/ResultExtensions.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"
#include "mozilla/dom/cache/DBSchema.h" #include "mozilla/dom/cache/DBSchema.h"
#include "mozilla/dom/cache/Manager.h" #include "mozilla/dom/cache/Manager.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.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::GetDirEntryKind;
using mozilla::dom::quota::nsIFileKind; using mozilla::dom::quota::nsIFileKind;
using mozilla::dom::quota::OriginMetadata; using mozilla::dom::quota::OriginMetadata;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::PrincipalMetadata;
using mozilla::dom::quota::PersistenceType;
using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::UsageInfo; using mozilla::dom::quota::UsageInfo;
using mozilla::ipc::AssertIsOnBackgroundThread; using mozilla::ipc::AssertIsOnBackgroundThread;
@ -123,7 +124,8 @@ Result<UsageInfo, nsresult> GetBodyUsage(nsIFile& aMorgueDir,
} }
Result<int64_t, nsresult> GetPaddingSizeFromDB( 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); CacheDirectoryMetadata directoryMetadata(aOriginMetadata);
// directoryMetadata.mDirectoryLockId must be -1 (which is default for new // directoryMetadata.mDirectoryLockId must be -1 (which is default for new
// CacheDirectoryMetadata) because this method should only be called from // CacheDirectoryMetadata) because this method should only be called from
@ -142,7 +144,7 @@ Result<int64_t, nsresult> GetPaddingSizeFromDB(
#endif #endif
QM_TRY_INSPECT(const auto& conn, 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 // 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 // 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. // XXX Ensure the -wel file is removed if the caches.sqlite doesn't exist.
QM_TRY(OkIf(!!cachesSQLiteFile), UsageInfo{}); 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( QM_TRY_INSPECT(
const auto& paddingSize, const auto& paddingSize,
([dir, cachesSQLiteFile, ([dir, cachesSQLiteFile, &aOriginMetadata,
&aOriginMetadata]() -> Result<int64_t, nsresult> { &maybeCipherKey]() -> Result<int64_t, nsresult> {
if (!DirectoryPaddingFileExists(*dir, DirPaddingFile::TMP_FILE)) { if (!DirectoryPaddingFileExists(*dir, DirPaddingFile::TMP_FILE)) {
QM_WARNONLY_TRY_UNWRAP(const auto maybePaddingSize, QM_WARNONLY_TRY_UNWRAP(const auto maybePaddingSize,
DirectoryPaddingGet(*dir)); DirectoryPaddingGet(*dir));
@ -251,8 +262,8 @@ Result<UsageInfo, nsresult> CacheQuotaClient::InitOrigin(
// If the temporary file still exists or failing to get the padding size // 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 // from the padding file, then we need to get the padding size from the
// database and restore the padding file. // database and restore the padding file.
QM_TRY_RETURN( QM_TRY_RETURN(GetPaddingSizeFromDB(*dir, *cachesSQLiteFile,
GetPaddingSizeFromDB(*dir, *cachesSQLiteFile, aOriginMetadata)); aOriginMetadata, maybeCipherKey));
}())); }()));
QM_TRY_INSPECT( QM_TRY_INSPECT(
@ -343,13 +354,20 @@ Result<UsageInfo, nsresult> CacheQuotaClient::GetUsageForOrigin(
QuotaManager* quotaManager = QuotaManager::Get(); QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager); MOZ_ASSERT(quotaManager);
return quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT, return quotaManager->GetUsageForClient(aOriginMetadata.mPersistenceType,
aOriginMetadata, Client::DOMCACHE); aOriginMetadata, Client::DOMCACHE);
} }
void CacheQuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType, void CacheQuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) { 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( void CacheQuotaClient::OnRepositoryClearCompleted(
@ -492,6 +510,20 @@ nsresult CacheQuotaClient::WipePaddingFileInternal(
return NS_OK; 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() { CacheQuotaClient::~CacheQuotaClient() {
AssertIsOnBackgroundThread(); AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(sInstance == this); MOZ_DIAGNOSTIC_ASSERT(sInstance == this);

View file

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

14
dom/cache/Types.h vendored
View file

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

View file

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

View file

@ -32,7 +32,7 @@ class DatabaseFileManager final
const nsString mDatabaseName; const nsString mDatabaseName;
const nsCString mDatabaseID; const nsCString mDatabaseID;
mutable IndexedDBCipherKeyManager mCipherKeyManager; RefPtr<IndexedDBCipherKeyManager> mCipherKeyManager;
LazyInitializedOnce<const nsString> mDirectoryPath; LazyInitializedOnce<const nsString> mDirectoryPath;
LazyInitializedOnce<const nsString> mJournalDirectoryPath; LazyInitializedOnce<const nsString> mJournalDirectoryPath;
@ -84,7 +84,10 @@ class DatabaseFileManager final
const nsCString& DatabaseID() const { return mDatabaseID; } const nsCString& DatabaseID() const { return mDatabaseID; }
IndexedDBCipherKeyManager& MutableCipherKeyManagerRef() const { IndexedDBCipherKeyManager& MutableCipherKeyManagerRef() const {
return mCipherKeyManager; MOZ_ASSERT(mIsInPrivateBrowsingMode);
MOZ_ASSERT(mCipherKeyManager);
return *mCipherKeyManager;
} }
auto IsInPrivateBrowsingMode() const { return mIsInPrivateBrowsingMode; } 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_ #ifndef DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_
#define DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_ #define DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_
#include "FlippedOnce.h" #include "mozilla/dom/quota/CipherKeyManager.h"
#include "mozilla/DataMutex.h"
#include "mozilla/dom/quota/IPCStreamCipherStrategy.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; using CipherKey = IndexedDBCipherStrategy::KeyType;
class IndexedDBCipherKeyManager { } // namespace mozilla::dom
// 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
#endif // DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_ #endif // DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_

View file

@ -69,7 +69,6 @@ UNIFIED_SOURCES += [
"IDBTransaction.cpp", "IDBTransaction.cpp",
"IndexedDatabase.cpp", "IndexedDatabase.cpp",
"IndexedDatabaseManager.cpp", "IndexedDatabaseManager.cpp",
"IndexedDBCipherKeyManager.cpp",
"IndexedDBCommon.cpp", "IndexedDBCommon.cpp",
"KeyPath.cpp", "KeyPath.cpp",
"ProfilerHelpers.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", "AssertionsImpl.h",
"CachingDatabaseConnection.h", "CachingDatabaseConnection.h",
"CheckedUnsafePtr.h", "CheckedUnsafePtr.h",
"CipherKeyManager.h",
"CipherStrategy.h", "CipherStrategy.h",
"Client.h", "Client.h",
"ClientImpl.h", "ClientImpl.h",