Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / netwerk / test / unit / test_httpssvc_retry_with_ech.js
blobf28e89ae6db37ee6921bb217353ef0fa0e6a7b2e
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var { setTimeout } = ChromeUtils.importESModule(
8 "resource://gre/modules/Timer.sys.mjs"
9 );
11 let trrServer;
12 let h3Port;
13 let h3EchConfig;
15 const certOverrideService = Cc[
16 "@mozilla.org/security/certoverride;1"
17 ].getService(Ci.nsICertOverrideService);
19 function checkSecurityInfo(chan, expectPrivateDNS, expectAcceptedECH) {
20 let securityInfo = chan.securityInfo;
21 Assert.equal(
22 securityInfo.isAcceptedEch,
23 expectAcceptedECH,
24 "ECH Status == Expected Status"
26 Assert.equal(
27 securityInfo.usedPrivateDNS,
28 expectPrivateDNS,
29 "Private DNS Status == Expected Status"
33 add_setup(async function setup() {
34 // Allow telemetry probes which may otherwise be disabled for some
35 // applications (e.g. Thunderbird).
36 Services.prefs.setBoolPref(
37 "toolkit.telemetry.testing.overrideProductsCheck",
38 true
41 trr_test_setup();
43 Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
44 Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
45 Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
46 Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
47 Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
48 Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
50 await asyncStartTLSTestServer(
51 "EncryptedClientHelloServer",
52 "../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
55 h3Port = Services.env.get("MOZHTTP3_PORT_ECH");
56 Assert.notEqual(h3Port, null);
57 Assert.notEqual(h3Port, "");
59 h3EchConfig = Services.env.get("MOZHTTP3_ECH");
60 Assert.notEqual(h3EchConfig, null);
61 Assert.notEqual(h3EchConfig, "");
62 });
64 registerCleanupFunction(async () => {
65 trr_clear_prefs();
66 Services.prefs.clearUserPref("network.trr.mode");
67 Services.prefs.clearUserPref("network.trr.uri");
68 Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
69 Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
70 Services.prefs.clearUserPref("network.dns.echconfig.enabled");
71 Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
72 Services.prefs.clearUserPref(
73 "network.dns.echconfig.fallback_to_origin_when_all_failed"
75 Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
76 Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
77 Services.prefs.clearUserPref("security.tls.ech.grease_http3");
78 Services.prefs.clearUserPref("security.tls.ech.grease_probability");
79 if (trrServer) {
80 await trrServer.stop();
82 });
84 function makeChan(url) {
85 let chan = NetUtil.newChannel({
86 uri: url,
87 loadUsingSystemPrincipal: true,
88 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
89 }).QueryInterface(Ci.nsIHttpChannel);
90 return chan;
93 function channelOpenPromise(chan, flags) {
94 return new Promise(resolve => {
95 function finish(req, buffer) {
96 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
97 false
99 resolve([req, buffer]);
101 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
102 true
104 chan.asyncOpen(new ChannelListener(finish, null, flags));
108 function ActivityObserver() {}
110 ActivityObserver.prototype = {
111 activites: [],
112 observeConnectionActivity(
113 aHost,
114 aPort,
115 aSSL,
116 aHasECH,
117 aIsHttp3,
118 aActivityType,
119 aActivitySubtype,
120 aTimestamp,
121 aExtraStringData
123 dump(
124 "*** Connection Activity 0x" +
125 aActivityType.toString(16) +
126 " 0x" +
127 aActivitySubtype.toString(16) +
128 " " +
129 aExtraStringData +
130 "\n"
132 this.activites.push({ host: aHost, subType: aActivitySubtype });
136 function checkHttpActivities(activites, expectECH) {
137 let foundDNSAndSocket = false;
138 let foundSettingECH = false;
139 let foundConnectionCreated = false;
140 for (let activity of activites) {
141 switch (activity.subType) {
142 case Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED:
143 case Ci.nsIHttpActivityObserver
144 .ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED:
145 foundDNSAndSocket = true;
146 break;
147 case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_ECH_SET:
148 foundSettingECH = true;
149 break;
150 case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_CONNECTION_CREATED:
151 foundConnectionCreated = true;
152 break;
153 default:
154 break;
158 Assert.equal(foundDNSAndSocket, true, "Should have one DnsAndSock created");
159 Assert.equal(foundSettingECH, expectECH, "Should have echConfig");
160 Assert.equal(
161 foundConnectionCreated,
162 true,
163 "Should have one connection created"
167 add_task(async function testConnectWithECH() {
168 const ECH_CONFIG_FIXED =
169 "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAEAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
170 trrServer = new TRRServer();
171 await trrServer.start();
173 let observerService = Cc[
174 "@mozilla.org/network/http-activity-distributor;1"
175 ].getService(Ci.nsIHttpActivityDistributor);
176 let observer = new ActivityObserver();
177 observerService.addObserver(observer);
178 observerService.observeConnection = true;
180 Services.prefs.setCharPref(
181 "network.trr.uri",
182 `https://foo.example.com:${trrServer.port()}/dns-query`
185 // Only the last record is valid to use.
186 await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
187 answers: [
189 name: "ech-private.example.com",
190 ttl: 55,
191 type: "HTTPS",
192 flush: false,
193 data: {
194 priority: 1,
195 name: "ech-private.example.com",
196 values: [
197 { key: "alpn", value: "http/1.1" },
198 { key: "port", value: 8443 },
200 key: "echconfig",
201 value: ECH_CONFIG_FIXED,
202 needBase64Decode: true,
210 await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
211 answers: [
213 name: "ech-private.example.com",
214 ttl: 55,
215 type: "A",
216 flush: false,
217 data: "127.0.0.1",
222 await new TRRDNSListener("ech-private.example.com", {
223 type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
226 HandshakeTelemetryHelpers.resetHistograms();
227 let chan = makeChan(`https://ech-private.example.com`);
228 await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
229 checkSecurityInfo(chan, true, true);
230 // Only check telemetry if network process is disabled.
231 if (!mozinfo.socketprocess_networking) {
232 HandshakeTelemetryHelpers.checkSuccess(["", "_ECH", "_FIRST_TRY"]);
233 HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
236 await trrServer.stop();
237 observerService.removeObserver(observer);
238 observerService.observeConnection = false;
240 let filtered = observer.activites.filter(
241 activity => activity.host === "ech-private.example.com"
243 checkHttpActivities(filtered, true);
246 add_task(async function testEchRetry() {
247 Services.obs.notifyObservers(null, "net:cancel-all-connections");
248 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
249 await new Promise(resolve => setTimeout(resolve, 1000));
251 Services.dns.clearCache(true);
253 const ECH_CONFIG_TRUSTED_RETRY =
254 "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAMAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
255 trrServer = new TRRServer();
256 await trrServer.start();
258 Services.prefs.setIntPref("network.trr.mode", 3);
259 Services.prefs.setCharPref(
260 "network.trr.uri",
261 `https://foo.example.com:${trrServer.port()}/dns-query`
264 // Only the last record is valid to use.
265 await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
266 answers: [
268 name: "ech-private.example.com",
269 ttl: 55,
270 type: "HTTPS",
271 flush: false,
272 data: {
273 priority: 1,
274 name: "ech-private.example.com",
275 values: [
276 { key: "alpn", value: "http/1.1" },
277 { key: "port", value: 8443 },
279 key: "echconfig",
280 value: ECH_CONFIG_TRUSTED_RETRY,
281 needBase64Decode: true,
289 await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
290 answers: [
292 name: "ech-private.example.com",
293 ttl: 55,
294 type: "A",
295 flush: false,
296 data: "127.0.0.1",
301 await new TRRDNSListener("ech-private.example.com", {
302 type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
305 Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
307 HandshakeTelemetryHelpers.resetHistograms();
308 let chan = makeChan(`https://ech-private.example.com`);
309 await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
310 checkSecurityInfo(chan, true, true);
311 // Only check telemetry if network process is disabled.
312 if (!mozinfo.socketprocess_networking) {
313 for (let hName of ["SSL_HANDSHAKE_RESULT", "SSL_HANDSHAKE_RESULT_ECH"]) {
314 let h = Services.telemetry.getHistogramById(hName);
315 HandshakeTelemetryHelpers.assertHistogramMap(
316 h.snapshot(),
317 new Map([
318 ["0", 1],
319 ["188", 1],
323 HandshakeTelemetryHelpers.checkEntry(["_FIRST_TRY"], 188, 1);
324 HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
327 await trrServer.stop();
330 async function H3ECHTest(
331 echConfig,
332 expectedHistKey,
333 expectedHistEntries,
334 advertiseECH
336 Services.dns.clearCache(true);
337 Services.obs.notifyObservers(null, "net:cancel-all-connections");
338 /* eslint-disable mozilla/no-arbitrary-setTimeout */
339 await new Promise(resolve => setTimeout(resolve, 1000));
340 resetEchTelemetry();
341 trrServer = new TRRServer();
342 await trrServer.start();
344 Services.prefs.setCharPref(
345 "network.trr.uri",
346 `https://foo.example.com:${trrServer.port()}/dns-query`
348 Services.prefs.setBoolPref("network.dns.port_prefixed_qname_https_rr", true);
350 let observerService = Cc[
351 "@mozilla.org/network/http-activity-distributor;1"
352 ].getService(Ci.nsIHttpActivityDistributor);
353 Services.obs.notifyObservers(null, "net:cancel-all-connections");
354 let observer = new ActivityObserver();
355 observerService.addObserver(observer);
356 observerService.observeConnection = true;
357 // Clear activities for past connections
358 observer.activites = [];
360 let portPrefixedName = `_${h3Port}._https.public.example.com`;
361 let vals = [
362 { key: "alpn", value: "h3-29" },
363 { key: "port", value: h3Port },
365 if (advertiseECH) {
366 vals.push({
367 key: "echconfig",
368 value: echConfig,
369 needBase64Decode: true,
372 // Only the last record is valid to use.
374 await trrServer.registerDoHAnswers(portPrefixedName, "HTTPS", {
375 answers: [
377 name: portPrefixedName,
378 ttl: 55,
379 type: "HTTPS",
380 flush: false,
381 data: {
382 priority: 1,
383 name: ".",
384 values: vals,
390 await trrServer.registerDoHAnswers("public.example.com", "A", {
391 answers: [
393 name: "public.example.com",
394 ttl: 55,
395 type: "A",
396 flush: false,
397 data: "127.0.0.1",
402 await new TRRDNSListener("public.example.com", {
403 type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
404 port: h3Port,
407 let chan = makeChan(`https://public.example.com:${h3Port}`);
408 let [req] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
409 req.QueryInterface(Ci.nsIHttpChannel);
410 Assert.equal(req.protocolVersion, "h3-29");
411 checkSecurityInfo(chan, true, advertiseECH);
413 await trrServer.stop();
415 observerService.removeObserver(observer);
416 observerService.observeConnection = false;
418 let filtered = observer.activites.filter(
419 activity => activity.host === "public.example.com"
421 checkHttpActivities(filtered, advertiseECH);
422 await checkEchTelemetry(expectedHistKey, expectedHistEntries);
425 function resetEchTelemetry() {
426 Services.telemetry.getKeyedHistogramById("HTTP3_ECH_OUTCOME").clear();
429 async function checkEchTelemetry(histKey, histEntries) {
430 Services.obs.notifyObservers(null, "net:cancel-all-connections");
431 /* eslint-disable mozilla/no-arbitrary-setTimeout */
432 await new Promise(resolve => setTimeout(resolve, 1000));
433 let values = Services.telemetry
434 .getKeyedHistogramById("HTTP3_ECH_OUTCOME")
435 .snapshot()[histKey];
436 if (!mozinfo.socketprocess_networking) {
437 HandshakeTelemetryHelpers.assertHistogramMap(values, histEntries);
441 add_task(async function testH3WithNoEch() {
442 Services.prefs.setBoolPref("security.tls.ech.grease_http3", false);
443 Services.prefs.setIntPref("security.tls.ech.grease_probability", 0);
444 await H3ECHTest(
445 h3EchConfig,
446 "NONE",
447 new Map([
448 ["0", 1],
449 ["1", 0],
451 false
455 add_task(async function testH3WithECH() {
456 await H3ECHTest(
457 h3EchConfig,
458 "REAL",
459 new Map([
460 ["0", 1],
461 ["1", 0],
463 true
467 add_task(async function testH3WithGreaseEch() {
468 Services.prefs.setBoolPref("security.tls.ech.grease_http3", true);
469 Services.prefs.setIntPref("security.tls.ech.grease_probability", 100);
470 await H3ECHTest(
471 h3EchConfig,
472 "GREASE",
473 new Map([
474 ["0", 1],
475 ["1", 0],
477 false
481 add_task(async function testH3WithECHRetry() {
482 Services.dns.clearCache(true);
483 Services.obs.notifyObservers(null, "net:cancel-all-connections");
484 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
485 await new Promise(resolve => setTimeout(resolve, 1000));
487 function base64ToArray(base64) {
488 var binary_string = atob(base64);
489 var len = binary_string.length;
490 var bytes = new Uint8Array(len);
491 for (var i = 0; i < len; i++) {
492 bytes[i] = binary_string.charCodeAt(i);
494 return bytes;
497 let decodedConfig = base64ToArray(h3EchConfig);
498 decodedConfig[6] ^= 0x94;
499 let encoded = btoa(String.fromCharCode.apply(null, decodedConfig));
500 await H3ECHTest(
501 encoded,
502 "REAL",
503 new Map([
504 ["0", 1],
505 ["1", 1],
507 true