Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'win' && os_version == '11.26100' && arch == 'x86_64' && msix OR os == 'win' && os_version == '11.26200' && arch == 'x86_64' && msix
- Manifest: netwerk/test/unit/xpcshell.toml
/* 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
// Tests that when a server sends a fatal decrypt_error alert on a TLS session
// resumption attempt (simulating a server whose session ticket encryption key
// 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_DECRYPT_ERROR_ALERT on the
//
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
);
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const kHost = "decrypt-error-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");
});
}
);
// server to send a fatal decrypt_error alert (e.g. server 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_decrypt_error() {
// 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 [, 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 decrypt_error alert — simulating a server whose
// STEK has rotated. Firefox must automatically retry with a full handshake
// (MaybeRemoveSSLToken evicts the stale token) and succeed.
{
let beforeCallbacks = resumeCallbackCount;
let [, buf] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
ok(buf, "second connection succeeded after decrypt_error retry");
equal(
resumeCallbackCount,
beforeCallbacks + 1,
"FaultyServer fired exactly once (on the resumption attempt)"
);
}
}
);