/* 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 that when a server sends a fatal illegal_parameter alert on a TLS // session resumption attempt (e.g. PSK binder verification failure on a // server whose STEK has rotated), Firefox automatically retries the // connection with a full handshake and succeeds. // // FaultyServer fires at epoch 1 read (early traffic secret), which fires only // when the client offers a PSK with early data. This requires // MOZ_TLS_SERVER_0RTT so the first connection's NewSessionTicket carries // maxEarlyDataSize > 0. The alert is sent before ServerHello at the // unencrypted record layer, producing SSL_ERROR_ILLEGAL_PARAMETER_ALERT on // the client — observed in the wild against Python wptserve (which sends // illegal_parameter rather than decrypt_error for stale PSKs). // // Companion to test_retry_decrypt_error.js. "use strict"; const { HttpServer } = ChromeUtils.importESModule( "resource://testing-common/httpd.sys.mjs" ); const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); const kHost = "illegal-parameter-on-resume.example.com"; var httpServer = null; let resumeCallbackCount = 0; add_setup( { skip_if: () => AppConstants.MOZ_SYSTEM_NSS, }, async () => { httpServer = new HttpServer(); httpServer.registerPathHandler("/callback/1", () => { resumeCallbackCount++; }); httpServer.start(-1); registerCleanupFunction(async () => { await httpServer.stop(); }); await asyncSetupFaultyServer(httpServer); Services.prefs.setCharPref("network.dns.localDomains", kHost); registerCleanupFunction(() => { Services.prefs.clearUserPref("network.dns.localDomains"); }); } ); // When a persisted TLS session ticket causes the server to send a fatal // illegal_parameter alert (e.g. PSK binder verification failure after STEK // rotation), Firefox must automatically retry the connection with a full // handshake instead of surfacing "Secure Connection Failed" to the user. add_task( { skip_if: () => AppConstants.MOZ_SYSTEM_NSS, }, async function test_retry_on_illegal_parameter() { // First connection: no cached session ticket → full TLS 1.3 handshake. // The server issues a NewSessionTicket (with maxEarlyDataSize > 0 because // MOZ_TLS_SERVER_0RTT is set), which Firefox caches in SSLTokensCache. // The FaultyServer callback does NOT fire here because the client has no // PSK to offer, so no early data is sent and epoch 1 is never derived. { let beforeCallbacks = resumeCallbackCount; let chan = makeChan(`https://${kHost}:8443/`); let [, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL); ok(buf, "first connection succeeded"); equal( resumeCallbackCount, beforeCallbacks, "FaultyServer did not fire on fresh handshake" ); } // The server has an anti-replay mechanism that prohibits it from accepting // 0-RTT connections immediately after issuing a ticket. await sleep(1); // Second connection: Firefox finds the cached session ticket, offers it as // a PSK, and sends early data. FaultyServer detects epoch 1 (early traffic // secret) and sends a fatal illegal_parameter alert — simulating a server // that fails PSK binder verification. Firefox must automatically retry // with a full handshake (MaybeRemoveSSLToken evicts the stale token) and // succeed. { let beforeCallbacks = resumeCallbackCount; let chan = makeChan(`https://${kHost}:8443/`); let [, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL); ok(buf, "second connection succeeded after illegal_parameter retry"); equal( resumeCallbackCount, beforeCallbacks + 1, "FaultyServer fired exactly once (on the resumption attempt)" ); } } );