Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* 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/. */
// Verifies that a TRRServiceChannel preemptively sends Proxy-Authorization
// when the nsIProxyInfo returned by the registered proxy filter carries a
// bearer token (as IPPChannelFilter constructs via newProxyInfo's
// aProxyAuthorizationHeader argument).
//
// The HTTP/2 proxy is started in auth-required mode: a CONNECT without a
// Proxy-Authorization header is answered with 407 and the stream is closed.
// TRRServiceChannel has no nsHttpChannelAuthProvider, so it cannot retry
// after a 407. Therefore a successful DNS resolution through the proxy is
// only possible if the channel includes Proxy-Authorization on the first
// CONNECT.
"use strict";
/* import-globals-from head_trr.js */
/* import-globals-from head_channels.js */
const { NodeHTTP2ProxyServer } = ChromeUtils.importESModule(
);
const BEARER_TOKEN = "Bearer trr-preemptive-test-token";
// Mirrors IPPChannelFilter.applyFilter: returns an nsIProxyInfo whose
// proxyAuthorizationHeader is the bearer token. The TRRServiceChannel under
// test should then copy that onto its request head before the CONNECT goes
// out (see TRRServiceChannel::OnProxyAvailable).
class BearerProxyChannelFilter {
QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyChannelFilter"]);
constructor(host, port, trrUriPrefix) {
this._host = host;
this._port = port;
this._trrUriPrefix = trrUriPrefix;
}
applyFilter(channel, defaultPI, cb) {
// Only proxy the TRR DoH endpoint. Anything else (e.g. cert revocation
// lookups for the proxy itself) stays direct.
if (!channel.URI.spec.startsWith(this._trrUriPrefix)) {
cb.onProxyFilterResult(defaultPI);
return;
}
const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(
Ci.nsIProtocolProxyService
);
cb.onProxyFilterResult(
pps.newProxyInfo(
"https",
this._host,
this._port,
BEARER_TOKEN, // aProxyAuthorizationHeader
"", // aConnectionIsolationKey
Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
10, // failover timeout
null
)
);
}
}
let pps;
let proxy;
let trrServer;
let filter;
add_setup(async function setup() {
trr_test_setup();
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
trrServer = new TRRServer();
await trrServer.start();
await trrServer.registerDoHAnswers("test.proxy.com", "A", {
answers: [
{
name: "test.proxy.com",
ttl: 55,
type: "A",
flush: false,
data: "3.3.3.3",
},
],
});
// Wrap the DoH handler so we can assert that the inner DoH request the
// server sees does NOT carry Proxy-Authorization. That header is for the
// proxy hop only and must never reach the origin.
await trrServer.execute(`
global.dnsQuerySawProxyAuth = false;
global.originalDnsQueryHandler = global.path_handlers["/dns-query"];
`);
await trrServer.registerPathHandler("/dns-query", function (req, resp) {
if (req.headers["proxy-authorization"]) {
global.dnsQuerySawProxyAuth = true;
}
return global.originalDnsQueryHandler(req, resp);
});
proxy = new NodeHTTP2ProxyServer();
// auth=true: reject CONNECT requests without Proxy-Authorization with 407.
// Skip the proxy's built-in proxy filter; we install our own that carries
// the bearer token.
await proxy.startWithoutProxyFilter(0, true);
pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(
Ci.nsIProtocolProxyService
);
filter = new BearerProxyChannelFilter(
"localhost",
proxy.port(),
`https://foo.example.com:${trrServer.port()}/dns-query`
);
pps.registerChannelFilter(filter, 0);
registerCleanupFunction(async () => {
if (filter) {
pps.unregisterChannelFilter(filter);
filter = null;
}
if (proxy) {
await proxy.stop();
proxy = null;
}
if (trrServer) {
await trrServer.stop();
trrServer = null;
}
trr_clear_prefs();
});
});
add_task(async function test_trr_preemptive_proxy_auth() {
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setIntPref(
"network.trr.request_timeout_mode_trronly_ms",
5000
);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port()}/dns-query`
);
Services.dns.clearCache(true);
// If the bearer is not attached to the CONNECT, the proxy answers 407 and
// TRR cannot retry — the listener would observe a failure here.
await new TRRDNSListener("test.proxy.com", "3.3.3.3");
Assert.ok(
!(await trrServer.execute("global.dnsQuerySawProxyAuth")),
"Proxy-Authorization must not leak through to the DoH server"
);
});