Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
# 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
import os
import re
import sys
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).parents[3] / "quota/test/marionette"))
from quota_test_case import QuotaTestCase
QM_ENCRYPTION_PREF = "dom.quotaManager.encryption.enabled"
QM_TESTING_PREF = "dom.quotaManager.testing"
class IDBEncryptionLockstore(QuotaTestCase):
"""
Verify that IndexedDB data is encrypted on disk when
dom.quotaManager.encryption.enabled is true (lockstore backend).
"""
def setUp(self):
super().setUp()
self.testHTML = "dom/indexedDB/basicIDB_PBM.html"
self.IDBName = "IDBTest"
self.IDBStoreName = "IDBTestStore"
self.IDBVersion = 1
self.IDBValue = "test_IDB_Encryption_Lockstore"
self.idbStoragePath = None
self.profilePath = self.marionette.instance.profile.profile
# QuotaManager reads encryption.enabled once at startup, so write it to
# the profile and restart before touching storage.
self.restartWithCleanStorage({
QM_ENCRYPTION_PREF: True,
QM_TESTING_PREF: True,
})
def tearDown(self):
super().tearDown()
def test_encrypted_IDB_sqlite_ondisk(self):
with self.using_new_window(self.testHTML, private=False) as (
self.origin,
self.persistenceType,
):
self.marionette.execute_async_script(
"""
const [idb, store, key, value, resolve] = arguments;
window.wrappedJSObject.addDataIntoIDB(idb, store, key, value).then(resolve);
""",
script_args=(
self.IDBName,
self.IDBStoreName,
"textKey",
self.IDBValue,
),
)
self.ensureInvariantHolds(lambda _: self.sqliteWALReleased())
self.ensureInvariantHolds(
lambda _: (
self.findDirObj(self.getIDBStoragePath(), ".sqlite", True)
is not None
)
)
sqliteDBFile = self.findDirObj(self.getIDBStoragePath(), ".sqlite", True)
with open(sqliteDBFile, "rb") as f:
foundRawValue = (
re.search(self.IDBValue.encode("ascii"), f.read()) is not None
)
self.assertFalse(foundRawValue, "IDB data on disk is not encrypted")
def test_encrypted_IDB_blob_ondisk(self):
with self.using_new_window(self.testHTML, private=False) as (
self.origin,
self.persistenceType,
):
self.marionette.execute_async_script(
"""
const [idb, store, key, value, resolve] = arguments;
const blobValue = new Blob([value], {type:'text/plain'});
window.wrappedJSObject.addDataIntoIDB(idb, store, key, blobValue).then(resolve);
""",
script_args=(
self.IDBName,
self.IDBStoreName,
"blobKey",
self.IDBValue,
),
)
self.ensureInvariantHolds(lambda _: self.sqliteWALReleased())
self.ensureInvariantHolds(
lambda _: (
self.findDirObj(self.getIDBStoragePath(), ".files", False)
is not None
)
)
idbBlobDir = self.findDirObj(self.getIDBStoragePath(), ".files", False)
idbBlobPath = os.path.join(idbBlobDir, "1")
self.ensureInvariantHolds(lambda _: os.path.exists(idbBlobPath))
with open(idbBlobPath, "rb") as f:
foundRawValue = (
re.search(self.IDBValue.encode("ascii"), f.read()) is not None
)
self.assertFalse(foundRawValue, "IDB blob data on disk is not encrypted")
def test_lockstore_keystore_exists(self):
with self.using_new_window(self.testHTML, private=False) as (
self.origin,
self.persistenceType,
):
self.marionette.execute_async_script(
"""
const [idb, store, key, value, resolve] = arguments;
window.wrappedJSObject.addDataIntoIDB(idb, store, key, value).then(resolve);
""",
script_args=(
self.IDBName,
self.IDBStoreName,
"textKey",
self.IDBValue,
),
)
lockstoreDb = os.path.join(self.profilePath, "lockstore.keys.sqlite")
self.ensureInvariantHolds(lambda _: os.path.exists(lockstoreDb))
self.assertTrue(
os.path.getsize(lockstoreDb) > 0,
"lockstore database is empty",
)
def getIDBStoragePath(self):
if self.idbStoragePath is not None:
return self.idbStoragePath
assert self.origin is not None
assert self.persistenceType is not None
self.idbStoragePath = self.getStoragePath(
self.profilePath, self.origin, self.persistenceType, "idb"
)
return self.idbStoragePath
def sqliteWALReleased(self):
if not os.path.exists(self.getIDBStoragePath()):
return False
walPath = self.findDirObj(self.idbStoragePath, ".sqlite-wal", True)
return walPath is None or os.stat(walPath).st_size == 0
class IDBEncryptionLockstoreControl(QuotaTestCase):
"""
Control test: with encryption disabled, IDB data is plaintext on disk.
Validates the encryption tests above are not vacuously passing.
"""
def setUp(self):
super().setUp()
self.testHTML = "dom/indexedDB/basicIDB_PBM.html"
self.IDBName = "IDBTest"
self.IDBStoreName = "IDBTestStore"
self.IDBVersion = 1
self.IDBValue = "test_IDB_No_Encryption_Control"
self.idbStoragePath = None
self.profilePath = self.marionette.instance.profile.profile
self.restartWithCleanStorage({
QM_ENCRYPTION_PREF: False,
QM_TESTING_PREF: True,
})
def tearDown(self):
super().tearDown()
def test_unencrypted_IDB_sqlite_ondisk(self):
with self.using_new_window(self.testHTML, private=False) as (
self.origin,
self.persistenceType,
):
self.marionette.execute_async_script(
"""
const [idb, store, key, value, resolve] = arguments;
window.wrappedJSObject.addDataIntoIDB(idb, store, key, value).then(resolve);
""",
script_args=(
self.IDBName,
self.IDBStoreName,
"textKey",
self.IDBValue,
),
)
self.ensureInvariantHolds(lambda _: self.sqliteWALReleased())
self.ensureInvariantHolds(
lambda _: (
self.findDirObj(self.getIDBStoragePath(), ".sqlite", True)
is not None
)
)
sqliteDBFile = self.findDirObj(self.getIDBStoragePath(), ".sqlite", True)
with open(sqliteDBFile, "rb") as f:
foundRawValue = (
re.search(self.IDBValue.encode("ascii"), f.read()) is not None
)
self.assertTrue(
foundRawValue,
"IDB data should be plaintext when encryption is disabled",
)
def getIDBStoragePath(self):
if self.idbStoragePath is not None:
return self.idbStoragePath
assert self.origin is not None
assert self.persistenceType is not None
self.idbStoragePath = self.getStoragePath(
self.profilePath, self.origin, self.persistenceType, "idb"
)
return self.idbStoragePath
def sqliteWALReleased(self):
if not os.path.exists(self.getIDBStoragePath()):
return False
walPath = self.findDirObj(self.idbStoragePath, ".sqlite-wal", True)
return walPath is None or os.stat(walPath).st_size == 0