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/. */
7 var { setTimeout
} = ChromeUtils
.importESModule(
8 "resource://gre/modules/Timer.sys.mjs"
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
;
22 securityInfo
.isAcceptedEch
,
24 "ECH Status == Expected Status"
27 securityInfo
.usedPrivateDNS
,
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",
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
, "");
64 registerCleanupFunction(async () => {
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");
80 await trrServer
.stop();
84 function makeChan(url
) {
85 let chan
= NetUtil
.newChannel({
87 loadUsingSystemPrincipal
: true,
88 contentPolicyType
: Ci
.nsIContentPolicy
.TYPE_DOCUMENT
,
89 }).QueryInterface(Ci
.nsIHttpChannel
);
93 function channelOpenPromise(chan
, flags
) {
94 return new Promise(resolve
=> {
95 function finish(req
, buffer
) {
96 certOverrideService
.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
99 resolve([req
, buffer
]);
101 certOverrideService
.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
104 chan
.asyncOpen(new ChannelListener(finish
, null, flags
));
108 function ActivityObserver() {}
110 ActivityObserver
.prototype = {
112 observeConnectionActivity(
124 "*** Connection Activity 0x" +
125 aActivityType
.toString(16) +
127 aActivitySubtype
.toString(16) +
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;
147 case Ci
.nsIHttpActivityDistributor
.ACTIVITY_SUBTYPE_ECH_SET
:
148 foundSettingECH
= true;
150 case Ci
.nsIHttpActivityDistributor
.ACTIVITY_SUBTYPE_CONNECTION_CREATED
:
151 foundConnectionCreated
= true;
158 Assert
.equal(foundDNSAndSocket
, true, "Should have one DnsAndSock created");
159 Assert
.equal(foundSettingECH
, expectECH
, "Should have echConfig");
161 foundConnectionCreated
,
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(
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", {
189 name
: "ech-private.example.com",
195 name
: "ech-private.example.com",
197 { key
: "alpn", value
: "http/1.1" },
198 { key
: "port", value
: 8443 },
201 value
: ECH_CONFIG_FIXED
,
202 needBase64Decode
: true,
210 await trrServer
.registerDoHAnswers("ech-private.example.com", "A", {
213 name
: "ech-private.example.com",
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(
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", {
268 name
: "ech-private.example.com",
274 name
: "ech-private.example.com",
276 { key
: "alpn", value
: "http/1.1" },
277 { key
: "port", value
: 8443 },
280 value
: ECH_CONFIG_TRUSTED_RETRY
,
281 needBase64Decode
: true,
289 await trrServer
.registerDoHAnswers("ech-private.example.com", "A", {
292 name
: "ech-private.example.com",
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(
323 HandshakeTelemetryHelpers
.checkEntry(["_FIRST_TRY"], 188, 1);
324 HandshakeTelemetryHelpers
.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
327 await trrServer
.stop();
330 async
function H3ECHTest(
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));
341 trrServer = new TRRServer();
342 await trrServer.start();
344 Services.prefs.setCharPref(
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`;
362 { key: "alpn", value: "h3-29" },
363 { key: "port", value: h3Port },
369 needBase64Decode: true,
372 // Only the last record is valid to use.
374 await trrServer.registerDoHAnswers(portPrefixedName, "HTTPS", {
377 name: portPrefixedName,
390 await trrServer.registerDoHAnswers("public.example.com", "A", {
393 name: "public.example.com",
402 await new TRRDNSListener("public.example.com", {
403 type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
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);
455 add_task(async function testH3WithECH() {
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);
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);
497 let decodedConfig = base64ToArray(h3EchConfig);
498 decodedConfig[6] ^= 0x94;
499 let encoded = btoa(String.fromCharCode.apply(null, decodedConfig));