1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
function isIsolated(key) {
return key.charAt(7) == "i";
}
function hasTopWindowOrigin(key, origin) {
let tokenAtEnd = `{{${origin}}}`;
let endPart = key.slice(-tokenAtEnd.length);
return endPart == tokenAtEnd;
}
function hasAnyTopWindowOrigin(key) {
return !!key.match(/{{[^}]+}}/);
}
function altSvcCacheKeyIsolated(parsed) {
return parsed.length > 5 && parsed[5] == "I";
}
function altSvcTopWindowOrigin(key) {
let index = -1;
for (let i = 0; i < 6; ++i) {
index = key.indexOf(":", index + 1);
}
let indexEnd = key.indexOf("|", index + 1);
return key.substring(index + 1, indexEnd);
}
const gHttpHandler = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler
);
add_task(async function() {
info("Starting tlsSessionTickets test");
await SpecialPowers.flushPrefEnv();
await SpecialPowers.pushPrefEnv({
set: [
["browser.cache.disk.enable", false],
["browser.cache.memory.enable", false],
[
"network.cookie.cookieBehavior",
Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
],
["network.http.altsvc.proxy_checks", false],
["privacy.trackingprotection.enabled", false],
["privacy.trackingprotection.pbmode.enabled", false],
["privacy.trackingprotection.annotate_channels", true],
["privacy.partition.network_state", false],
],
});
await UrlClassifierTestUtils.addTestTrackers();
info("Creating a new tab");
let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tab;
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
const trackingURL =
"https://tlsresumptiontest.example.org/browser/toolkit/components/antitracking/test/browser/empty-altsvc.js";
const topWindowOrigin = TEST_DOMAIN.replace(/\/$/, "");
const kTopic = "http-on-examine-response";
let resumedState = [];
let hashKeys = [];
function observer(subject, topic, data) {
if (topic != kTopic) {
return;
}
subject.QueryInterface(Ci.nsIChannel);
if (subject.URI.spec != trackingURL) {
return;
}
resumedState.push(
subject.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo).resumed
);
hashKeys.push(
subject.QueryInterface(Ci.nsIHttpChannelInternal).connectionInfoHashKey
);
}
Services.obs.addObserver(observer, kTopic);
registerCleanupFunction(() => Services.obs.removeObserver(observer, kTopic));
function checkAltSvcCache(expectedCount, expectedIsolated) {
let arr = gHttpHandler.altSvcCacheKeys;
is(
arr.length,
expectedCount,
"Found the expected number of items in the cache"
);
for (let i = 0; i < arr.length; ++i) {
let key = arr[i];
let parsed = key.split(":");
if (altSvcCacheKeyIsolated(parsed)) {
ok(expectedIsolated[i], "We expected to find an isolated item");
is(
altSvcTopWindowOrigin(key),
topWindowOrigin,
"Expected top window origin found in the Alt-Svc cache key"
);
} else {
ok(!expectedIsolated[i], "We expected to find a non-isolated item");
}
}
}
checkAltSvcCache(0, []);
info("Loading tracking scripts and tracking images");
await SpecialPowers.spawn(browser, [{ trackingURL }], async function(obj) {
let src = content.document.createElement("script");
let p = new content.Promise(resolve => {
src.onload = resolve;
});
content.document.body.appendChild(src);
src.src = obj.trackingURL;
await p;
});
checkAltSvcCache(1, [true]);
// Load our tracking URL two more times, but this time in the first-party context.
// The TLS session should be resumed the second time here.
await fetch(trackingURL);
// Wait a little bit before issuing the second load to ensure both don't happen
// at the same time.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 10));
checkAltSvcCache(2, [false, true]);
await fetch(trackingURL).then(() => {
checkAltSvcCache(2, [false, true]);
is(
resumedState.length,
3,
"We should have observed 3 loads for " + trackingURL
);
ok(!resumedState[0], "The first load should NOT have been resumed");
ok(!resumedState[1], "The second load should NOT have been resumed");
ok(resumedState[2], "The third load SHOULD have been resumed");
// We also verify that the hashKey of the first connection is different to
// both the second and third connections, and that the hashKey of the
// second and third connections are the same. The reason why this check is
// done is that the private bit on the connection info object is used to
// construct the hash key, so when the connection is isolated because it
// comes from a third-party tracker context, its hash key must be
// different.
is(
hashKeys.length,
3,
"We should have observed 3 loads for " + trackingURL
);
is(hashKeys[1], hashKeys[2], "The second and third hash keys should match");
isnot(
hashKeys[0],
hashKeys[1],
"The first and second hash keys should not match"
);
ok(isIsolated(hashKeys[0]), "The first connection must have been isolated");
ok(
!isIsolated(hashKeys[1]),
"The second connection must not have been isolated"
);
ok(
!isIsolated(hashKeys[2]),
"The third connection must not have been isolated"
);
ok(
hasTopWindowOrigin(hashKeys[0], topWindowOrigin),
"The first connection must be bound to its top-level window"
);
ok(
!hasAnyTopWindowOrigin(hashKeys[1]),
"The second connection must not be bound to a top-level window"
);
ok(
!hasAnyTopWindowOrigin(hashKeys[2]),
"The third connection must not be bound to a top-level window"
);
});
info("Removing the tab");
BrowserTestUtils.removeTab(tab);
UrlClassifierTestUtils.cleanupTestTrackers();
});
add_task(async function() {
info("Cleaning up.");
await new Promise(resolve => {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
resolve()
);
});
});
|