The keystore previously closed at quit-application (AppShutdownConfirmed), the earliest shutdown phase, which forced obfsvfs to cache the raw per-file DEK (ObfsFile::aKey plus WAL/journal partner-inheritance) so it could finalize a connection's journals after the keystore was already gone. That early teardown was justified by a comment claiming LateWriteChecks fires at AppShutdownNetTeardown; it actually fires at XPCOMShutdownThreads (toolkit.shutdown.lateWriteChecksStage=2), so there is ample headroom to keep the keystore alive much later. Keep lockstore available for late profile writes and shut it down last, at XPCOMWillShutdown -- after Places (profile-before-change) and QuotaManager/IndexedDB/DOM-storage (profile-before-change-qm) have closed their connections, and after AppShutdownTelemetry (past which nothing writes the profile). Both the storage-internal keystore handle (SQLiteEncryption) and the LockstoreService XPCOM handle now tear down on xpcom-will-shutdown; InitEncryptionKeystore refuses to register/open once already in or beyond XPCOMWillShutdown. With lockstore alive that late, obfsvfs no longer needs to cache the DEK: WAL and journal opens fall through to the policy branch and re-derive the same per-database key from lockstore on demand (DeriveMainDbPath strips the -wal/-journal suffix), including the final checkpoint at a connection's close during shutdown. Remove ObfsFile::aKey, the partner-inheritance fast path, and the now-dead keyReady flag; pPartner is retained for checkpoint coordination. Differential Revision: https://phabricator.services.mozilla.com/D306037
133 lines
6.1 KiB
C++
133 lines
6.1 KiB
C++
/* 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 mozilla_security_lockstore_LockstoreService_h
|
|
#define mozilla_security_lockstore_LockstoreService_h
|
|
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/Result.h"
|
|
#include "mozilla/security/lockstore/lockstore_ffi_generated.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsILockstore.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsString.h"
|
|
#include "nsTArray.h"
|
|
|
|
namespace mozilla::security::lockstore {
|
|
|
|
class LockstoreService final : public nsILockstore, public nsIObserver {
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSILOCKSTORE
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
LockstoreService();
|
|
|
|
// Registered as the singleton's `init_method` in components.conf.
|
|
nsresult Init();
|
|
|
|
// Reachable from C++ consumers without going through XPCOM. The
|
|
// keystore is locked and closed at `profile-before-change` /
|
|
// `xpcom-shutdown` via the registered observers; the service object
|
|
// itself is released when the XPCOM component manager drops its
|
|
// singleton ref. Callers may hold a `RefPtr` past shutdown, but
|
|
// operations on it will fail with `NS_ERROR_NOT_AVAILABLE`.
|
|
static already_AddRefed<LockstoreService> GetSingleton();
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Synchronous C++ tier
|
|
//
|
|
// In-tree off-main-thread C++ consumers (mls_gk, SQLite encryption,
|
|
// …) call these directly. Each method invokes the FFI on the calling
|
|
// thread under `mMutex` and returns the result. Debug builds assert
|
|
// off-main-thread; callers on the main thread should wrap with
|
|
// `NS_DispatchBackgroundTask` themselves.
|
|
//
|
|
// The XPCOM tier (`NS_IMETHODIMP …` methods declared by
|
|
// `NS_DECL_NSILOCKSTORE`) is implemented on top of these via
|
|
// `ImplXpcomMethod`, which performs the dispatch + DOM-Promise bridge
|
|
// for JS callers.
|
|
// ---------------------------------------------------------------------
|
|
|
|
nsresult DoUnlockKek(const nsACString& aKekRef, const nsACString& aSecret,
|
|
uint32_t aTimeoutMs);
|
|
nsresult DoLockKek(const nsACString& aKekRef);
|
|
nsresult DoLock();
|
|
nsresult DoCreateDek(const nsACString& aCollection, const nsACString& aKekRef,
|
|
bool aExtractable, uint32_t aKeySize);
|
|
nsresult DoImportDek(const nsACString& aCollection, const nsACString& aKekRef,
|
|
const nsTArray<uint8_t>& aDekBytes, bool aExtractable);
|
|
Result<bool, nsresult> DoIsDekExtractable(const nsACString& aCollection);
|
|
nsresult DoDeleteDek(const nsACString& aCollection);
|
|
nsresult DoAddKek(const nsACString& aCollection,
|
|
const nsACString& aFromKekRef, const nsACString& aToKekRef);
|
|
nsresult DoRemoveKek(const nsACString& aCollection,
|
|
const nsACString& aKekRef);
|
|
nsresult DoSwitchKek(const nsACString& aCollection,
|
|
const nsACString& aOldKekRef,
|
|
const nsACString& aNewKekRef);
|
|
Result<nsTArray<nsCString>, nsresult> DoListDeks();
|
|
Result<nsTArray<nsCString>, nsresult> DoListKeks(const nsACString& aDekName);
|
|
Result<nsTArray<uint8_t>, nsresult> DoEncrypt(
|
|
const nsACString& aCollection, const nsACString& aKekRef,
|
|
const nsTArray<uint8_t>& aPlaintext);
|
|
Result<nsTArray<uint8_t>, nsresult> DoDecrypt(
|
|
const nsACString& aCollection, const nsACString& aKekRef,
|
|
const nsTArray<uint8_t>& aCiphertext);
|
|
Result<nsTArray<uint8_t>, nsresult> DoGetDek(const nsACString& aCollection,
|
|
const nsACString& aKekRef);
|
|
Result<nsCString, nsresult> DoCreateKek(const nsACString& aKekType,
|
|
const nsACString& aIdentifier,
|
|
const nsACString& aSecret,
|
|
uint32_t aCacheTimeoutMs);
|
|
nsresult DoDeleteKek(const nsACString& aKekRef);
|
|
|
|
private:
|
|
~LockstoreService();
|
|
|
|
// Opens the keystore against the current profile if not already open.
|
|
// Called under mMutex by every FFI-touching method.
|
|
nsresult EnsureOpenLocked() MOZ_REQUIRES(mMutex);
|
|
|
|
// Resolve and cache `mProfilePath` on the main thread. No-op if the profile
|
|
// is not yet available (early startup, before profile selection) or the path
|
|
// is already cached. Idempotent; called from `Init()` and on
|
|
// `profile-do-change`.
|
|
void CacheProfilePathOnMainThread();
|
|
|
|
// Protects mKeystore and mShutdown. Held across every FFI call so a
|
|
// shutdown racing with an in-flight operation cannot free the handle
|
|
// out from under the worker. Serialises every call into the FFI:
|
|
// concurrent XPCOM / C++ direct callers queue on this mutex, which
|
|
// is why no dedicated serial event target is needed.
|
|
//
|
|
// Lock ordering: acquired *before* any call into the Rust FFI, which
|
|
// has its own `Keystore::connection_lock`. Always mMutex first, then
|
|
// any Rust-side lock — never the reverse.
|
|
Mutex mMutex;
|
|
|
|
// FFI handle for the per-profile keystore. Null between construction
|
|
// and the first FFI dispatch; opened lazily by `EnsureOpenLocked`
|
|
// and closed in `Observe()` (on shutdown) and the destructor.
|
|
KeystoreHandle* mKeystore MOZ_GUARDED_BY(mMutex);
|
|
|
|
// Latches true on the first `profile-before-change` /
|
|
// `xpcom-shutdown` notification. Subsequent calls through
|
|
// `EnsureOpenLocked` short-circuit with `NS_ERROR_NOT_AVAILABLE`, so
|
|
// no new FFI work starts after shutdown.
|
|
bool mShutdown MOZ_GUARDED_BY(mMutex);
|
|
|
|
// Cached UTF-8 absolute path of the keystore parent directory (`<profile>`).
|
|
// Resolved on the main thread -- in `Init()` if the profile is already
|
|
// available, otherwise on `profile-do-change` -- because
|
|
// `nsIDirectoryService::Get` asserts main-thread while the sync `Do*` tier
|
|
// runs off-main. Guarded by mMutex: it may be written on the main thread at
|
|
// `profile-do-change` while a `Do*` call reads it off-main.
|
|
nsCString mProfilePath MOZ_GUARDED_BY(mMutex);
|
|
};
|
|
|
|
} // namespace mozilla::security::lockstore
|
|
|
|
#endif // mozilla_security_lockstore_LockstoreService_h
|