/* 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/. */ // Tests for Bug 2035453: SSLTokensCache persistence. // // Verifies that ssl_tokens_cache.bin is written to the profile directory // for each of the three triggers: // 1. "idle-daily" — once-a-day write // 2. "application-background" — Android backgrounding write // 3. profile-before-change via AsyncShutdown — on-quit write // // The network.ssl_tokens_cache_persistence pref must be in the toml [prefs] // block so that SSLTokensCache::Init() sees it when the IO service starts // (triggered by head.js before any test code runs). // // profile-after-change fires during the test infrastructure setup; our // Observe() handler sets mBackingFile and registers the shutdown blocker. "use strict"; const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); const { AsyncShutdown } = ChromeUtils.importESModule( "resource://gre/modules/AsyncShutdown.sys.mjs" ); let gProfileDir = null; let gCacheFile = null; add_setup({ skip_if: () => AppConstants.MOZ_SYSTEM_NSS }, async () => { const { HttpServer } = ChromeUtils.importESModule( "resource://testing-common/httpd.sys.mjs" ); // head_http3.js and head_trr.js call do_get_profile() at load time, setting // _profileInitialized = true. A subsequent do_get_profile(true) early- // returns without firing profile-after-change. Fire it explicitly so that // SSLTokensCache::Observe() sets mBackingFile and schedules the shutdown // blocker. gProfileDir = do_get_profile(); gCacheFile = PathUtils.join(gProfileDir.path, "ssl_tokens_cache.bin"); Services.obs.notifyObservers( null, "profile-after-change", "xpcshell-persist-test" ); // Yield so async setup triggered by the profile-after-change handler // (background load, etc.) has a chance to execute. await new Promise(resolve => do_timeout(0, resolve)); let httpServer = new HttpServer(); httpServer.registerPathHandler("/", (req, resp) => { resp.setStatusLine(req.httpVersion, 200, "OK"); resp.setHeader("Content-Type", "text/plain"); resp.bodyOutputStream.write("OK", 2); }); httpServer.start(-1); registerCleanupFunction(async () => httpServer.stop()); await asyncSetupFaultyServer(httpServer); }); // Makes one HTTPS connection to a FaultyServer host. The first connection // always succeeds and the server issues a NewSessionTicket (MOZ_TLS_SERVER_0RTT // is set by asyncSetupFaultyServer), populating the in-memory cache. async function makeConnection() { const kHost = "decrypt-error-on-resume.example.com"; Services.prefs.setCharPref("network.dns.localDomains", kHost); registerCleanupFunction(() => Services.prefs.clearUserPref("network.dns.localDomains") ); let chan = makeChan(`https://${kHost}:8443/`); let [, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL); ok(buf, "connection succeeded and NewSessionTicket was issued"); } // Polls until ssl_tokens_cache.bin appears (or the timeout is reached). async function waitForCacheFile() { for (let i = 0; i < 50; i++) { if (await IOUtils.exists(gCacheFile)) { return true; } await new Promise(resolve => do_timeout(100, resolve)); } return false; } // --- Test 1: idle-daily -------------------------------------------------- add_task( { skip_if: () => AppConstants.MOZ_SYSTEM_NSS }, async function test_ssl_token_cache_written_on_idle_daily() { await makeConnection(); await IOUtils.remove(gCacheFile, { ignoreAbsent: true }); Services.obs.notifyObservers(null, "idle-daily"); ok( await waitForCacheFile(), "ssl_tokens_cache.bin written after idle-daily" ); const info = await IOUtils.stat(gCacheFile); Assert.greater( info.size, 0, `cache file is non-empty (${info.size} bytes)` ); } ); // --- Test 2: application-background ------------------------------------- add_task( { skip_if: () => AppConstants.MOZ_SYSTEM_NSS }, async function test_ssl_token_cache_written_on_application_background() { await makeConnection(); await IOUtils.remove(gCacheFile, { ignoreAbsent: true }); Services.obs.notifyObservers(null, "application-background"); ok( await waitForCacheFile(), "ssl_tokens_cache.bin written after application-background" ); const info = await IOUtils.stat(gCacheFile); Assert.greater( info.size, 0, `cache file is non-empty (${info.size} bytes)` ); } ); // --- Test 3: profile-before-change (on-quit) ---------------------------- add_task( { skip_if: () => AppConstants.MOZ_SYSTEM_NSS }, async function test_ssl_token_cache_written_on_quit() { await makeConnection(); await IOUtils.remove(gCacheFile, { ignoreAbsent: true }); // Simulate the profile-before-change shutdown phase. The async shutdown // blocker registered by SSLTokensCache calls BlockShutdown, which // dispatches DoWrite(true) to the write task queue. _trigger() awaits // all blockers, so when it resolves the write has completed. Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); registerCleanupFunction(() => Services.prefs.clearUserPref("toolkit.asyncshutdown.testing") ); await AsyncShutdown.profileBeforeChange._trigger(); ok( await IOUtils.exists(gCacheFile), "ssl_tokens_cache.bin written after profile-before-change" ); const info = await IOUtils.stat(gCacheFile); Assert.greater( info.size, 0, `cache file is non-empty (${info.size} bytes)` ); } );