Bug 1671885 - Add a test case for ech retry r=necko-reviewers,dragana

Differential Revision: https://phabricator.services.mozilla.com/D97992
This commit is contained in:
Kershaw Chang 2020-12-07 13:45:20 +00:00
Родитель 7fe2cdbb1f
Коммит af36035662
12 изменённых файлов: 714 добавлений и 59 удалений

Просмотреть файл

@ -936,5 +936,27 @@ bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) {
return true;
}
void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) {
if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) {
return;
}
const nsCString& echConfig = aConnInfo->GetEchConfig();
if (mConnInfo->GetEchConfig().Equals(echConfig)) {
return;
}
LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n",
mConnInfo->HashKey().get()));
mConnInfo->SetEchConfig(echConfig);
// If echConfig is changed, we should close all half opens and idle
// connections. This is to make sure the new echConfig will be used for the
// next connection.
CloseAllHalfOpens();
CloseIdleConnections();
}
} // namespace net
} // namespace mozilla

Просмотреть файл

@ -185,6 +185,8 @@ class ConnectionEntry {
bool RemoveTransFromPendingQ(nsHttpTransaction* aTrans);
void MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo);
private:
void InsertIntoIdleConnections_internal(nsHttpConnection* conn);
void RemoveFromIdleConnectionsIndex(size_t inx);

Просмотреть файл

@ -378,7 +378,7 @@ nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord(
clone = new nsHttpConnectionInfo(
mOrigin, mOriginPort, alpn ? Get<0>(*alpn) : EmptyCString(), mUsername,
mTopWindowOrigin, mProxyInfo, mOriginAttributes, name,
port ? *port : mRoutedPort, mIsolated, isHttp3);
port ? *port : mOriginPort, mIsolated, isHttp3);
}
// Make sure the anonymous, insecure-scheme, and private flags are transferred

Просмотреть файл

@ -1627,6 +1627,10 @@ nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
trans->Caps() & NS_HTTP_DISALLOW_HTTP3);
MOZ_ASSERT(ent);
if (gHttpHandler->EchConfigEnabled()) {
ent->MaybeUpdateEchConfig(ci);
}
ReportProxyTelemetry(ent);
// Check if the transaction already has a sticky reference to a connection.
@ -3444,36 +3448,25 @@ void nsHttpConnectionMgr::MoveToWildCardConnEntry(
ent->MoveConnection(proxyConn, wcEnt);
}
bool nsHttpConnectionMgr::MoveTransToNewConnEntry(nsHttpTransaction* aTrans,
nsHttpConnectionInfo* aNewCI,
bool aNoHttp3ForNewEntry) {
bool nsHttpConnectionMgr::MoveTransToNewConnEntry(
nsHttpTransaction* aTrans, nsHttpConnectionInfo* aNewCI) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(
("nsHttpConnectionMgr::MoveTransToNewConnEntry: trans=%p aNewCI=%s "
"aNoHttp3ForNewEntry=%d",
aTrans, aNewCI->HashKey().get(), aNoHttp3ForNewEntry));
LOG(("nsHttpConnectionMgr::MoveTransToNewConnEntry: trans=%p aNewCI=%s",
aTrans, aNewCI->HashKey().get()));
bool prohibitWildCard = !!aTrans->TunnelProvider();
bool noHttp3 = aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3;
bool noHttp2 = aTrans->Caps() & NS_HTTP_DISALLOW_SPDY;
// Step 1: Check if the new entry is the same as the old one.
ConnectionEntry* oldEntry = GetOrCreateConnectionEntry(
aTrans->ConnectionInfo(), prohibitWildCard, noHttp2, noHttp3);
ConnectionEntry* newEntry = GetOrCreateConnectionEntry(
aNewCI, prohibitWildCard, noHttp2, noHttp3 || aNoHttp3ForNewEntry);
if (oldEntry == newEntry) {
return true;
}
// Step 2: Try to find the undispatched transaction.
if (!oldEntry->RemoveTransFromPendingQ(aTrans)) {
// Step 1: Get the transaction's connection entry.
ConnectionEntry* entry = mCT.GetWeak(aTrans->ConnectionInfo()->HashKey());
if (!entry) {
return false;
}
// Step 3: Add the transaction to the new entry.
// Step 2: Try to find the undispatched transaction.
if (!entry->RemoveTransFromPendingQ(aTrans)) {
return false;
}
// Step 3: Add the transaction.
aTrans->UpdateConnectionInfo(aNewCI);
Unused << ProcessNewTransaction(aTrans);
return true;

Просмотреть файл

@ -75,8 +75,7 @@ class nsHttpConnectionMgr final : public HttpConnectionMgrShell,
// one. Returns true if the transaction is moved successfully, otherwise
// returns false.
bool MoveTransToNewConnEntry(nsHttpTransaction* aTrans,
nsHttpConnectionInfo* aNewCI,
bool aNoHttp3ForNewEntry = false);
nsHttpConnectionInfo* aNewCI);
// This is used to force an idle connection to be closed and removed from
// the idle connection list. It is called when the idle connection detects

Просмотреть файл

@ -64,11 +64,6 @@ static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
// looking for a response header
#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
// TODO: These values should be removed when bug 1654332 is landed.
#define MOCK_SSL_ERROR_ECH_RETRY_WITH_ECH (SSL_ERROR_BASE + 188)
#define MOCK_SSL_ERROR_ECH_RETRY_WITHOUT_ECH (SSL_ERROR_BASE + 189)
#define MOCK_SSL_ERROR_ECH_FAILED (SSL_ERROR_BASE + 190)
using namespace mozilla::net;
namespace mozilla {
@ -1262,8 +1257,7 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
}
});
if (aReason ==
psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry"));
failedConnInfo->SetEchConfig(EmptyCString());
failedConnInfo.swap(mConnInfo);
@ -1271,8 +1265,8 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
return;
}
if (aReason == psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_RETRY_WITH_ECH)) {
LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use retry echConfig"));
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig"));
MOZ_ASSERT(mConnection);
nsCOMPtr<nsISupports> secInfo;
@ -1297,10 +1291,10 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
// Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but
// also for all failure cases.
if (aReason == psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_FAILED) ||
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED) ||
NS_FAILED(aReason)) {
LOG((" Got SSL_ERROR_ECH_FAILED, try other records"));
if (aReason == psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_FAILED)) {
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED)) {
id = Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT;
}
if (mRecordsForRetry.IsEmpty()) {
@ -1347,6 +1341,28 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
}
}
void nsHttpTransaction::MaybeReportFailedSVCDomain(
nsresult aReason, nsHttpConnectionInfo* aFailedConnInfo) {
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH) ||
aReason != psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
return;
}
Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
ErrorCodeToFailedReason(aReason));
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
if (dns) {
const nsCString& failedHost = aFailedConnInfo->GetRoutedHost().IsEmpty()
? aFailedConnInfo->GetOrigin()
: aFailedConnInfo->GetRoutedHost();
LOG(("add failed domain name [%s] -> [%s] to exclusion list",
aFailedConnInfo->GetOrigin().get(), failedHost.get()));
Unused << dns->ReportFailedSVCDomainName(aFailedConnInfo->GetOrigin(),
failedHost);
}
}
void nsHttpTransaction::Close(nsresult reason) {
LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this,
static_cast<uint32_t>(reason)));
@ -1512,16 +1528,7 @@ void nsHttpTransaction::Close(nsresult reason) {
!reallySentData || connReused)) ||
restartToFallbackConnInfo) {
if (restartToFallbackConnInfo) {
Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
ErrorCodeToFailedReason(reason));
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
if (dns) {
LOG(("add failed domain name [%s] -> [%s] to exclusion list",
mConnInfo->GetOrigin().get(), mConnInfo->GetRoutedHost().get()));
Unused << dns->ReportFailedSVCDomainName(mConnInfo->GetOrigin(),
mConnInfo->GetRoutedHost());
}
MaybeReportFailedSVCDomain(reason, mConnInfo);
PrepareConnInfoForRetry(reason);
mDontRetryWithDirectRoute = true;
LOG(
@ -3293,8 +3300,8 @@ void nsHttpTransaction::HandleFallback(
LOG(("nsHttpTransaction %p HandleFallback to connInfo[%s]", this,
aFallbackConnInfo->HashKey().get()));
bool foundInPendingQ = gHttpHandler->ConnMgr()->MoveTransToNewConnEntry(
this, aFallbackConnInfo, true);
bool foundInPendingQ =
gHttpHandler->ConnMgr()->MoveTransToNewConnEntry(this, aFallbackConnInfo);
if (!foundInPendingQ) {
MOZ_ASSERT(false, "transaction not in entry");
return;

Просмотреть файл

@ -228,6 +228,9 @@ class nsHttpTransaction final : public nsAHttpTransaction,
already_AddRefed<nsHttpConnectionInfo> PrepareFastFallbackConnInfo(
bool aEchConfigUsed);
void MaybeReportFailedSVCDomain(nsresult aReason,
nsHttpConnectionInfo* aFailedConnInfo);
already_AddRefed<Http2PushedStreamWrapper> TakePushedStreamById(
uint32_t aStreamId);

Просмотреть файл

@ -351,3 +351,106 @@ function deserialize_from_escaped_string(str) {
objectInStream.setInputStream(pipe.inputStream);
return objectInStream.readObject(true);
}
// Copied from head_psm.js.
function add_tls_server_setup(serverBinName, certsPath, addDefaultRoot = true) {
add_test(function() {
_setupTLSServerTest(serverBinName, certsPath, addDefaultRoot);
});
}
// Do not call this directly; use add_tls_server_setup
function _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot) {
asyncStartTLSTestServer(serverBinName, certsPath, addDefaultRoot).then(
run_next_test
);
}
async function asyncStartTLSTestServer(
serverBinName,
certsPath,
addDefaultRoot
) {
const { HttpServer } = ChromeUtils.import(
"resource://testing-common/httpd.js"
);
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
// The trusted CA that is typically used for "good" certificates.
if (addDefaultRoot) {
addCertFromFile(certdb, `${certsPath}/test-ca.pem`, "CTu,u,u");
}
const CALLBACK_PORT = 8444;
let envSvc = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile);
envSvc.set("DYLD_LIBRARY_PATH", greBinDir.path);
// TODO(bug 1107794): Android libraries are in /data/local/xpcb, but "GreBinD"
// does not return this path on Android, so hard code it here.
envSvc.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb");
envSvc.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3");
envSvc.set("MOZ_TLS_SERVER_CALLBACK_PORT", CALLBACK_PORT);
let httpServer = new HttpServer();
let serverReady = new Promise(resolve => {
httpServer.registerPathHandler("/", function handleServerCallback(
aRequest,
aResponse
) {
aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
aResponse.setHeader("Content-Type", "text/plain");
let responseBody = "OK!";
aResponse.bodyOutputStream.write(responseBody, responseBody.length);
executeSoon(function() {
httpServer.stop(resolve);
});
});
httpServer.start(CALLBACK_PORT);
});
let serverBin = _getBinaryUtil(serverBinName);
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(serverBin);
let certDir = do_get_file(certsPath, false);
Assert.ok(certDir.exists(), `certificate folder (${certsPath}) should exist`);
// Using "sql:" causes the SQL DB to be used so we can run tests on Android.
process.run(false, ["sql:" + certDir.path, Services.appinfo.processID], 2);
registerCleanupFunction(function() {
process.kill();
});
await serverReady;
}
function _getBinaryUtil(binaryUtilName) {
let utilBin = Services.dirsvc.get("GreD", Ci.nsIFile);
// On macOS, GreD is .../Contents/Resources, and most binary utilities
// are located there, but certutil is in GreBinD (or .../Contents/MacOS),
// so we have to change the path accordingly.
if (binaryUtilName === "certutil") {
utilBin = Services.dirsvc.get("GreBinD", Ci.nsIFile);
}
utilBin.append(binaryUtilName + mozinfo.bin_suffix);
// If we're testing locally, the above works. If not, the server executable
// is in another location.
if (!utilBin.exists()) {
utilBin = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
while (utilBin.path.includes("xpcshell")) {
utilBin = utilBin.parent;
}
utilBin.append("bin");
utilBin.append(binaryUtilName + mozinfo.bin_suffix);
}
// But maybe we're on Android, where binaries are in /data/local/xpcb.
if (!utilBin.exists()) {
utilBin.initWithPath("/data/local/xpcb/");
utilBin.append(binaryUtilName);
}
Assert.ok(utilBin.exists(), `Binary util ${binaryUtilName} should exist`);
return utilBin;
}

Просмотреть файл

@ -0,0 +1,286 @@
/* 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/. */
"use strict";
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
let prefs;
let h2Port;
let listen;
let trrServer;
const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
Ci.nsIDNSService
);
const certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
Ci.nsIThreadManager
);
const mainThread = threadManager.currentThread;
const defaultOriginAttributes = {};
function setup() {
let env = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
h2Port = env.get("MOZHTTP2_PORT");
Assert.notEqual(h2Port, null);
Assert.notEqual(h2Port, "");
// Set to allow the cert presented by our H2 server
do_get_profile();
prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
prefs.setBoolPref("network.security.esni.enabled", false);
prefs.setBoolPref("network.http.spdy.enabled", true);
prefs.setBoolPref("network.http.spdy.enabled.http2", true);
// the TRR server is on 127.0.0.1
prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
// make all native resolve calls "secretly" resolve localhost instead
prefs.setBoolPref("network.dns.native-is-localhost", true);
// 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
prefs.setIntPref("network.trr.mode", 2); // TRR first
prefs.setBoolPref("network.trr.wait-for-portal", false);
// don't confirm that TRR is working, just go!
prefs.setCharPref("network.trr.confirmationNS", "skip");
// So we can change the pref without clearing the cache to check a pushed
// record with a TRR path that fails.
Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
// The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
// so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
add_tls_server_setup(
"EncryptedClientHelloServer",
"../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
);
}
setup();
registerCleanupFunction(() => {
prefs.clearUserPref("network.security.esni.enabled");
prefs.clearUserPref("network.http.spdy.enabled");
prefs.clearUserPref("network.http.spdy.enabled.http2");
prefs.clearUserPref("network.dns.localDomains");
prefs.clearUserPref("network.dns.native-is-localhost");
prefs.clearUserPref("network.trr.mode");
prefs.clearUserPref("network.trr.uri");
prefs.clearUserPref("network.trr.credentials");
prefs.clearUserPref("network.trr.wait-for-portal");
prefs.clearUserPref("network.trr.allow-rfc1918");
prefs.clearUserPref("network.trr.useGET");
prefs.clearUserPref("network.trr.confirmationNS");
prefs.clearUserPref("network.trr.bootstrapAddress");
prefs.clearUserPref("network.trr.request-timeout");
prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
prefs.clearUserPref("network.dns.upgrade_with_https_rr");
prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
prefs.clearUserPref("network.dns.echconfig.enabled");
prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
trrServer.stop();
});
class DNSListener {
constructor() {
this.promise = new Promise(resolve => {
this.resolve = resolve;
});
}
onLookupComplete(inRequest, inRecord, inStatus) {
this.resolve([inRequest, inRecord, inStatus]);
}
// So we can await this as a promise.
then() {
return this.promise.then.apply(this.promise, arguments);
}
}
DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIDNSListener",
]);
function makeChan(url) {
let chan = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
return chan;
}
function channelOpenPromise(chan, flags) {
return new Promise(resolve => {
function finish(req, buffer) {
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
false
);
resolve([req, buffer]);
}
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
true
);
let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
internal.setWaitForHTTPSSVCRecord();
chan.asyncOpen(new ChannelListener(finish, null, flags));
});
}
add_task(async function testConnectWithECH() {
const ECH_CONFIG_FIXED =
"AEr+CABGABZlY2gtcHVibGljLmV4YW1wbGUuY29tACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAgAAQAAQADADIAAA==";
trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
// Only the last record is valid to use.
await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", [
{
name: "ech-private.example.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "ech-private.example.com",
values: [
{ key: "alpn", value: "http/1.1" },
{ key: "port", value: 8443 },
{
key: "echconfig",
value: ECH_CONFIG_FIXED,
needBase64Decode: true,
},
],
},
},
]);
await trrServer.registerDoHAnswers("ech-private.example.com", "A", [
{
name: "ech-private.example.com",
ttl: 55,
type: "A",
flush: false,
data: "127.0.0.1",
},
]);
let listener = new DNSListener();
let request = dns.asyncResolve(
"ech-private.example.com",
dns.RESOLVE_TYPE_HTTPSSVC,
0,
null, // resolverInfo
listener,
mainThread,
defaultOriginAttributes
);
let [inRequest, inRecord, inStatus] = await listener;
Assert.equal(inRequest, request, "correct request was used");
Assert.equal(inStatus, Cr.NS_OK, "status OK");
let chan = makeChan(`https://ech-private.example.com`);
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
let securityInfo = chan.securityInfo.QueryInterface(
Ci.nsITransportSecurityInfo
);
Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
await trrServer.stop();
});
add_task(async function testEchRetry() {
dns.clearCache(true);
const ECH_CONFIG_TRUSTED_RETRY =
"AEr+CABGABZlY2gtcHVibGljLmV4YW1wbGUuY29tACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAgAAQAAQABADIAAA==";
trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
// Only the last record is valid to use.
await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", [
{
name: "ech-private.example.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "ech-private.example.com",
values: [
{ key: "alpn", value: "http/1.1" },
{ key: "port", value: 8443 },
{
key: "echconfig",
value: ECH_CONFIG_TRUSTED_RETRY,
needBase64Decode: true,
},
],
},
},
]);
await trrServer.registerDoHAnswers("ech-private.example.com", "A", [
{
name: "ech-private.example.com",
ttl: 55,
type: "A",
flush: false,
data: "127.0.0.1",
},
]);
let listener = new DNSListener();
let request = dns.asyncResolve(
"ech-private.example.com",
dns.RESOLVE_TYPE_HTTPSSVC,
0,
null, // resolverInfo
listener,
mainThread,
defaultOriginAttributes
);
let [inRequest, inRecord, inStatus] = await listener;
Assert.equal(inRequest, request, "correct request was used");
Assert.equal(inStatus, Cr.NS_OK, "status OK");
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
let chan = makeChan(`https://ech-private.example.com`);
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
let securityInfo = chan.securityInfo.QueryInterface(
Ci.nsITransportSecurityInfo
);
Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
await trrServer.stop();
});

Просмотреть файл

@ -0,0 +1,221 @@
/* 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/. */
"use strict";
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
let prefs;
let h2Port;
let listen;
let trrServer;
const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
Ci.nsIDNSService
);
const certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
Ci.nsIThreadManager
);
const mainThread = threadManager.currentThread;
const defaultOriginAttributes = {};
function setup() {
let env = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
h2Port = env.get("MOZHTTP2_PORT");
Assert.notEqual(h2Port, null);
Assert.notEqual(h2Port, "");
// Set to allow the cert presented by our H2 server
do_get_profile();
prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
prefs.setBoolPref("network.security.esni.enabled", false);
prefs.setBoolPref("network.http.spdy.enabled", true);
prefs.setBoolPref("network.http.spdy.enabled.http2", true);
// the TRR server is on 127.0.0.1
prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
// make all native resolve calls "secretly" resolve localhost instead
prefs.setBoolPref("network.dns.native-is-localhost", true);
// 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
prefs.setIntPref("network.trr.mode", 2); // TRR first
prefs.setBoolPref("network.trr.wait-for-portal", false);
// don't confirm that TRR is working, just go!
prefs.setCharPref("network.trr.confirmationNS", "skip");
// So we can change the pref without clearing the cache to check a pushed
// record with a TRR path that fails.
Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
// The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
// so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
// An arbitrary, non-ECH server.
add_tls_server_setup(
"DelegatedCredentialsServer",
"../../../security/manager/ssl/tests/unit/test_delegated_credentials"
);
let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
nssComponent.clearSSLExternalAndInternalSessionCache();
}
setup();
registerCleanupFunction(() => {
prefs.clearUserPref("network.security.esni.enabled");
prefs.clearUserPref("network.http.spdy.enabled");
prefs.clearUserPref("network.http.spdy.enabled.http2");
prefs.clearUserPref("network.dns.localDomains");
prefs.clearUserPref("network.dns.native-is-localhost");
prefs.clearUserPref("network.trr.mode");
prefs.clearUserPref("network.trr.uri");
prefs.clearUserPref("network.trr.credentials");
prefs.clearUserPref("network.trr.wait-for-portal");
prefs.clearUserPref("network.trr.allow-rfc1918");
prefs.clearUserPref("network.trr.useGET");
prefs.clearUserPref("network.trr.confirmationNS");
prefs.clearUserPref("network.trr.bootstrapAddress");
prefs.clearUserPref("network.trr.request-timeout");
prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
prefs.clearUserPref("network.dns.upgrade_with_https_rr");
prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
prefs.clearUserPref("network.dns.echconfig.enabled");
prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
trrServer.stop();
});
class DNSListener {
constructor() {
this.promise = new Promise(resolve => {
this.resolve = resolve;
});
}
onLookupComplete(inRequest, inRecord, inStatus) {
this.resolve([inRequest, inRecord, inStatus]);
}
// So we can await this as a promise.
then() {
return this.promise.then.apply(this.promise, arguments);
}
}
DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIDNSListener",
]);
function makeChan(url) {
let chan = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
return chan;
}
function channelOpenPromise(chan, flags) {
return new Promise(resolve => {
function finish(req, buffer) {
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
false
);
resolve([req, buffer]);
}
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
false
);
let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
internal.setWaitForHTTPSSVCRecord();
chan.asyncOpen(new ChannelListener(finish, null, flags));
});
}
add_task(async function testRetryWithoutECH() {
const ECH_CONFIG_FIXED =
"AFH+CABNAB1kZWxlZ2F0ZWQtZW5hYmxlZC5leGFtcGxlLmNvbQAgigdWOUn6xiMpNu1vNsT6c1kw7N6u9nNOMUrqw1pW/QoAIAAEAAEAAwAyAAA=";
trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
// Only the last record is valid to use.
await trrServer.registerDoHAnswers(
"delegated-disabled.example.com",
"HTTPS",
[
{
name: "delegated-disabled.example.com",
ttl: 55,
type: "HTTPS",
flush: false,
data: {
priority: 1,
name: "delegated-disabled.example.com",
values: [
{
key: "echconfig",
value: ECH_CONFIG_FIXED,
needBase64Decode: true,
},
],
},
},
]
);
await trrServer.registerDoHAnswers("delegated-disabled.example.com", "A", [
{
name: "delegated-disabled.example.com",
ttl: 55,
type: "A",
flush: false,
data: "127.0.0.1",
},
]);
let listener = new DNSListener();
let request = dns.asyncResolve(
"delegated-disabled.example.com",
dns.RESOLVE_TYPE_HTTPSSVC,
0,
null, // resolverInfo
listener,
mainThread,
defaultOriginAttributes
);
let [inRequest, inRecord, inStatus] = await listener;
Assert.equal(inRequest, request, "correct request was used");
Assert.equal(inStatus, Cr.NS_OK, "status OK");
let chan = makeChan(`https://delegated-disabled.example.com:8443`);
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
let securityInfo = chan.securityInfo.QueryInterface(
Ci.nsITransportSecurityInfo
);
Assert.ok(
!securityInfo.isAcceptedEch,
"This host should not have accepted ECH"
);
await trrServer.stop();
});

Просмотреть файл

@ -495,4 +495,7 @@ run-sequentially = node server exceptions dont replay well
skip-if = asan || tsan || os == 'win' || os =='android' || socketprocess_networking
run-sequentially = node server exceptions dont replay well
[test_cookie_ipv6.js]
[test_httpssvc_retry_with_ech.js]
skip-if = asan || tsan || os == 'win' || os =='android'
[test_httpssvc_retry_without_ech.js]
skip-if = asan || tsan || os == 'win' || os =='android'

Просмотреть файл

@ -1392,13 +1392,23 @@ svcparam.encode = function(param, buf, offset) {
svcparam.encode.bytes += 4;
}
} else if (key == 5) { //echconfig
// TODO: base64 presentation format
buf.writeUInt16BE(param.value.length, offset);
offset += 2;
svcparam.encode.bytes += 2;
buf.write(param.value, offset);
offset += param.value.length;
svcparam.encode.bytes += param.value.length;
if (svcparam.ech) {
buf.writeUInt16BE(svcparam.ech.length, offset);
offset += 2;
svcparam.encode.bytes += 2;
for (let i = 0; i < svcparam.ech.length; i++) {
buf.writeUInt8(svcparam.ech[i], offset);
offset++;
}
svcparam.encode.bytes += svcparam.ech.length;
} else {
buf.writeUInt16BE(param.value.length, offset);
offset += 2;
svcparam.encode.bytes += 2;
buf.write(param.value, offset);
offset += param.value.length;
svcparam.encode.bytes += param.value.length;
}
} else if (key == 6) { //ipv6hint
let val = param.value;
if (!Array.isArray(val)) val = [val];
@ -1458,7 +1468,13 @@ svcparam.encodingLength = function (param) {
case 'no-default-alpn' : return 4
case 'port' : return 4 + 2
case 'ipv4hint' : return 4 + 4 * (Array.isArray(param.value) ? param.value.length : 1)
case 'echconfig' : return 4 + param.value.length
case 'echconfig' : {
if (param.needBase64Decode) {
svcparam.ech = Buffer.from(param.value, "base64");
return 4 + svcparam.ech.length;
}
return 4 + param.value.length
}
case 'ipv6hint' : return 4 + 16 * (Array.isArray(param.value) ? param.value.length : 1)
case 'key65535' : return 4
default: return 4 // unknown option