diff --git a/dom/cache/Action.h b/dom/cache/Action.h index 1219c58e249c..e432f2a197ce 100644 --- a/dom/cache/Action.h +++ b/dom/cache/Action.h @@ -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 { virtual void RunOnTarget( SafeRefPtr aResolver, const Maybe& aDirectoryMetadata, - Data* aOptionalData) = 0; + Data* aOptionalData, const Maybe& aMaybeCipherKey) = 0; // Called on initiating thread when the Action is canceled. The Action is // responsible for calling Resolver::Resolve() as normal; either with a diff --git a/dom/cache/CacheCipherKeyManager.h b/dom/cache/CacheCipherKeyManager.h new file mode 100644 index 000000000000..8444b2027cc3 --- /dev/null +++ b/dom/cache/CacheCipherKeyManager.h @@ -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; +using CipherKey = CipherStrategy::KeyType; + +} // namespace mozilla::dom::cache + +#endif // DOM_CACHE_CACHECIPHERKEYMANAGER_H_ diff --git a/dom/cache/Context.cpp b/dom/cache/Context.cpp index 3fe4508a2557..7f37a670e70e 100644 --- a/dom/cache/Context.cpp +++ b/dom/cache/Context.cpp @@ -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 aResolver, - const mozilla::Maybe&, - Data*) override { + const mozilla::Maybe&, Data*, + const mozilla::Maybe& + /* 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 mPrincipalInfo; Maybe mDirectoryMetadata; RefPtr mDirectoryLock; + RefPtr mCipherKeyManager; State mState; Atomic 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 aContext, Data* aData, nsISerialEventTarget* aTarget, SafeRefPtr aAction, - const Maybe& aDirectoryMetadata) + const Maybe& aDirectoryMetadata, + RefPtr 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 mTarget; SafeRefPtr mAction; const Maybe mDirectoryMetadata; + RefPtr mCipherKeyManager; nsCOMPtr 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 Context::MaybeDirectoryLockRef() const { return ToMaybeRef(mDirectoryLock.get()); } +CipherKeyManager& Context::MutableCipherKeyManagerRef() { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + MOZ_DIAGNOSTIC_ASSERT(mCipherKeyManager); + + return *mCipherKeyManager; +} + +const Maybe& 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 aAction, bool aDoomData) { NS_ASSERT_OWNINGTHREAD(Context); - auto runnable = - MakeSafeRefPtr(SafeRefPtrFromThis(), mData, mTarget, - std::move(aAction), mDirectoryMetadata); + auto runnable = MakeSafeRefPtr( + SafeRefPtrFromThis(), mData, mTarget, std::move(aAction), + mDirectoryMetadata, mCipherKeyManager); if (aDoomData) { mData = nullptr; @@ -980,7 +1015,8 @@ void Context::DispatchAction(SafeRefPtr aAction, bool aDoomData) { void Context::OnQuotaInit( nsresult aRv, const Maybe& aDirectoryMetadata, - already_AddRefed aDirectoryLock) { + already_AddRefed aDirectoryLock, + already_AddRefed 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 diff --git a/dom/cache/Context.h b/dom/cache/Context.h index a276dff04199..187de2964946 100644 --- a/dom/cache/Context.h +++ b/dom/cache/Context.h @@ -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 { Maybe MaybeDirectoryLockRef() const; + CipherKeyManager& MutableCipherKeyManagerRef(); + + const Maybe& 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 { void DispatchAction(SafeRefPtr aAction, bool aDoomData = false); void OnQuotaInit(nsresult aRv, const Maybe& aDirectoryMetadata, - already_AddRefed aDirectoryLock); + already_AddRefed aDirectoryLock, + already_AddRefed aCipherKeyManager); SafeRefPtr CreateThreadsafeHandle(); @@ -206,6 +212,7 @@ class Context final : public SafeRefCounted { SafeRefPtr mThreadsafeHandle; RefPtr mDirectoryLock; + RefPtr mCipherKeyManager; SafeRefPtr mNextContext; public: diff --git a/dom/cache/DBAction.cpp b/dom/cache/DBAction.cpp index 0a51260fe59e..dc5cdb316a67 100644 --- a/dom/cache/DBAction.cpp +++ b/dom/cache/DBAction.cpp @@ -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 aResolver, const Maybe& aDirectoryMetadata, - Data* aOptionalData) { + Data* aOptionalData, const Maybe& 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, nsresult> DBAction::OpenConnection( - const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir) { + const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir, + const Maybe& aMaybeCipherKey) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata.mDirectoryLockId >= 0); @@ -124,7 +121,7 @@ Result, 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, nsresult> OpenDBConnection( - const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile) { + const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile, + const Maybe& 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, 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 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, diff --git a/dom/cache/DBAction.h b/dom/cache/DBAction.h index 27b1c489cc49..4bd0c860643f 100644 --- a/dom/cache/DBAction.h +++ b/dom/cache/DBAction.h @@ -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, nsresult> OpenDBConnection( - const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile); + const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBFile, + const Maybe& aMaybeCipherKey); class DBAction : public Action { protected: @@ -41,10 +43,12 @@ class DBAction : public Action { private: void RunOnTarget(SafeRefPtr aResolver, const Maybe& aDirectoryMetadata, - Data* aOptionalData) override; + Data* aOptionalData, + const Maybe& aMaybeCipherKey) override; Result, nsresult> OpenConnection( - const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir); + const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aDBDir, + const Maybe& aMaybeCipherKey); const Mode mMode; }; diff --git a/dom/cache/FileUtils.cpp b/dom/cache/FileUtils.cpp index 9c6b1f60e63e..fe4f5676bc0a 100644 --- a/dom/cache/FileUtils.cpp +++ b/dom/cache/FileUtils.cpp @@ -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>, nsresult> BodyIdToFile(nsIFile& aBaseDir, @@ -128,22 +137,15 @@ nsresult BodyDeleteDir(const CacheDirectoryMetadata& aDirectoryMetadata, return NS_OK; } -Result>, nsresult> BodyStartWriteStream( +Result, nsresult> BodyStartWriteStream( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, + const nsID& aBodyId, Maybe 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, - 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>, nsresult> BodyStartWriteStream( } QM_TRY_INSPECT(const auto& tmpFile, - BodyIdToFile(aBaseDir, id, BODY_FILE_TMP)); + BodyIdToFile(aBaseDir, aBodyId, BODY_FILE_TMP)); - QM_TRY_UNWRAP( - nsCOMPtr fileStream, - CreateFileOutputStream(PERSISTENCE_TYPE_DEFAULT, aDirectoryMetadata, - Client::DOMCACHE, tmpFile.get())); + QM_TRY_UNWRAP(nsCOMPtr fileStream, + CreateFileOutputStream(aDirectoryMetadata.mPersistenceType, + aDirectoryMetadata, Client::DOMCACHE, + tmpFile.get())); + + const auto privateBody = aDirectoryMetadata.mIsPrivate; + if (privateBody) { + MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey); + + fileStream = MakeRefPtr>( + std::move(fileStream), kEncryptedStreamBlockSize, *aMaybeCipherKey); + } const auto compressed = MakeRefPtr(fileStream); @@ -172,7 +182,7 @@ Result>, 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>, nsresult> BodyOpen( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, - const nsID& aId) { + const nsID& aId, Maybe aMaybeCipherKey) { QM_TRY_INSPECT(const auto& finalFile, BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL)); - QM_TRY_RETURN(CreateFileInputStream(PERSISTENCE_TYPE_DEFAULT, + QM_TRY_UNWRAP(nsCOMPtr fileInputStream, + CreateFileInputStream(aDirectoryMetadata.mPersistenceType, aDirectoryMetadata, Client::DOMCACHE, finalFile.get())); + + auto privateBody = aDirectoryMetadata.mIsPrivate; + if (privateBody) { + MOZ_DIAGNOSTIC_ASSERT(aMaybeCipherKey); + + fileInputStream = new quota::DecryptingInputStream( + 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 = 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 { - nsID fileId; + nsID fileId{}; QM_TRY(OkIf(fileId.Parse(leafName.BeginReading())), true, ([&aDirectoryMetadata, &bodyFile](const auto) { DebugOnly result = RemoveNsIFile( diff --git a/dom/cache/FileUtils.h b/dom/cache/FileUtils.h index e22a08908d3b..d86cb910268f 100644 --- a/dom/cache/FileUtils.h +++ b/dom/cache/FileUtils.h @@ -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>, nsresult> BodyStartWriteStream( +Result, nsresult> BodyStartWriteStream( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, + const nsID& aBodyId, Maybe aMaybeCipherKey, nsIInputStream& aSource, void* aClosure, nsAsyncCopyCallbackFun aCallback); void BodyCancelWrite(nsISupports& aCopyContext); @@ -44,7 +46,7 @@ nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId); Result>, nsresult> BodyOpen( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, - const nsID& aId); + const nsID& aId, Maybe aMaybeCipherKey); nsresult BodyMaybeUpdatePaddingSize( const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile& aBaseDir, diff --git a/dom/cache/Manager.cpp b/dom/cache/Manager.cpp index 5975000e3141..30fc4d194068 100644 --- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -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 GetOrCreateCipherKey(NotNull 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 aResolver, const Maybe& aDirectoryMetadata, - Data*) override { + Data*, + const Maybe& /*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 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 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 mRequestStream; - nsID mRequestBodyId; + nsID mRequestBodyId{}; nsCOMPtr mRequestCopyContext; CacheResponse mResponse; nsCOMPtr mResponseStream; - nsID mResponseBodyId; + nsID mResponseBodyId{}; nsCOMPtr 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, + 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 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 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 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; } diff --git a/dom/cache/QuotaClient.cpp b/dom/cache/QuotaClient.cpp index f2e0af70a022..f869e080eb02 100644 --- a/dom/cache/QuotaClient.cpp +++ b/dom/cache/QuotaClient.cpp @@ -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 GetBodyUsage(nsIFile& aMorgueDir, } Result GetPaddingSizeFromDB( - nsIFile& aDir, nsIFile& aDBFile, const OriginMetadata& aOriginMetadata) { + nsIFile& aDir, nsIFile& aDBFile, const OriginMetadata& aOriginMetadata, + const Maybe& 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 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 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 maybeCipherKey; + auto cipherKeyManager = GetOrCreateCipherKeyManager(aOriginMetadata); + if (cipherKeyManager) { + maybeCipherKey = Some(cipherKeyManager->Ensure()); + } + return maybeCipherKey; + }(); + QM_TRY_INSPECT( const auto& paddingSize, - ([dir, cachesSQLiteFile, - &aOriginMetadata]() -> Result { + ([dir, cachesSQLiteFile, &aOriginMetadata, + &maybeCipherKey]() -> Result { if (!DirectoryPaddingFileExists(*dir, DirPaddingFile::TMP_FILE)) { QM_WARNONLY_TRY_UNWRAP(const auto maybePaddingSize, DirectoryPaddingGet(*dir)); @@ -251,8 +262,8 @@ Result 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 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 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); diff --git a/dom/cache/QuotaClientImpl.h b/dom/cache/QuotaClientImpl.h index 62001ce8d87c..de481194314d 100644 --- a/dom/cache/QuotaClientImpl.h +++ b/dom/cache/QuotaClientImpl.h @@ -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 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> mCipherKeyManagers; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheQuotaClient, override) }; diff --git a/dom/cache/Types.h b/dom/cache/Types.h index b033f0cea4c0..0e66b13984d5 100644 --- a/dom/cache/Types.h +++ b/dom/cache/Types.h @@ -10,6 +10,7 @@ #include #include #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 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); } }; diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index b56ad8f2cf87..660f1fe25833 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -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())); diff --git a/dom/indexedDB/DatabaseFileManager.h b/dom/indexedDB/DatabaseFileManager.h index 03138b49a26c..62d11e95a5d6 100644 --- a/dom/indexedDB/DatabaseFileManager.h +++ b/dom/indexedDB/DatabaseFileManager.h @@ -32,7 +32,7 @@ class DatabaseFileManager final const nsString mDatabaseName; const nsCString mDatabaseID; - mutable IndexedDBCipherKeyManager mCipherKeyManager; + RefPtr mCipherKeyManager; LazyInitializedOnce mDirectoryPath; LazyInitializedOnce 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; } diff --git a/dom/indexedDB/IndexedDBCipherKeyManager.cpp b/dom/indexedDB/IndexedDBCipherKeyManager.cpp deleted file mode 100644 index 1cb0b22b4f6f..000000000000 --- a/dom/indexedDB/IndexedDBCipherKeyManager.cpp +++ /dev/null @@ -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 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 diff --git a/dom/indexedDB/IndexedDBCipherKeyManager.h b/dom/indexedDB/IndexedDBCipherKeyManager.h index 3e4125bee15e..ac46cb888061 100644 --- a/dom/indexedDB/IndexedDBCipherKeyManager.h +++ b/dom/indexedDB/IndexedDBCipherKeyManager.h @@ -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; 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 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> mCipherKeys; - - FlippedOnce mInvalidated; -}; - -} // namespace mozilla::dom::indexedDB +} // namespace mozilla::dom #endif // DOM_INDEXEDDB_INDEXEDDBCIPHERKEYMANAGER_H_ diff --git a/dom/indexedDB/moz.build b/dom/indexedDB/moz.build index 69be79a0d842..286f6d1d217f 100644 --- a/dom/indexedDB/moz.build +++ b/dom/indexedDB/moz.build @@ -69,7 +69,6 @@ UNIFIED_SOURCES += [ "IDBTransaction.cpp", "IndexedDatabase.cpp", "IndexedDatabaseManager.cpp", - "IndexedDBCipherKeyManager.cpp", "IndexedDBCommon.cpp", "KeyPath.cpp", "ProfilerHelpers.cpp", diff --git a/dom/quota/CipherKeyManager.h b/dom/quota/CipherKeyManager.h new file mode 100644 index 000000000000..abafc25dd9da --- /dev/null +++ b/dom/quota/CipherKeyManager.h @@ -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 +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 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> mCipherKeys; + + FlippedOnce mInvalidated; +}; + +} // namespace mozilla::dom::quota + +#endif // DOM_QUOTA_CIPHERKEYMANAGER_H_ diff --git a/dom/quota/moz.build b/dom/quota/moz.build index 4e33f40b5e3e..5525ba92e1c5 100644 --- a/dom/quota/moz.build +++ b/dom/quota/moz.build @@ -29,6 +29,7 @@ EXPORTS.mozilla.dom.quota += [ "AssertionsImpl.h", "CachingDatabaseConnection.h", "CheckedUnsafePtr.h", + "CipherKeyManager.h", "CipherStrategy.h", "Client.h", "ClientImpl.h",