test_ech_grease.js (7475B)
1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 // Any copyright is dedicated to the Public Domain. 3 // http://creativecommons.org/publicdomain/zero/1.0/ 4 5 "use strict"; 6 7 // Allow telemetry probes which may otherwise be disabled for some 8 // applications (e.g. Thunderbird). 9 Services.prefs.setBoolPref( 10 "toolkit.telemetry.testing.overrideProductsCheck", 11 true 12 ); 13 14 // Get a profile directory and ensure PSM initializes NSS. 15 do_get_profile(); 16 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); 17 18 class InputStreamCallback { 19 constructor(output) { 20 this.output = output; 21 this.stopped = false; 22 } 23 24 onInputStreamReady(stream) { 25 info("input stream ready"); 26 if (this.stopped) { 27 info("input stream callback stopped - bailing"); 28 return; 29 } 30 let available = 0; 31 try { 32 available = stream.available(); 33 } catch (e) { 34 // onInputStreamReady may fire when the stream has been closed. 35 equal( 36 e.result, 37 Cr.NS_BASE_STREAM_CLOSED, 38 "error should be NS_BASE_STREAM_CLOSED" 39 ); 40 } 41 if (available > 0) { 42 let request = NetUtil.readInputStreamToString(stream, available, { 43 charset: "utf8", 44 }); 45 ok( 46 request.startsWith("GET / HTTP/1.1\r\n"), 47 "Should get a simple GET / HTTP/1.1 request" 48 ); 49 let response = 50 "HTTP/1.1 200 OK\r\n" + 51 "Content-Length: 2\r\n" + 52 "Content-Type: text/plain\r\n" + 53 "\r\nOK"; 54 let written = this.output.write(response, response.length); 55 equal( 56 written, 57 response.length, 58 "should have been able to write entire response" 59 ); 60 } 61 this.output.close(); 62 info("done with input stream ready"); 63 } 64 65 stop() { 66 this.stopped = true; 67 this.output.close(); 68 } 69 } 70 71 class TLSServerSecurityObserver { 72 constructor(input, output) { 73 this.input = input; 74 this.output = output; 75 this.callbacks = []; 76 this.stopped = false; 77 } 78 79 onHandshakeDone(socket, status) { 80 info("TLS handshake done"); 81 info(`TLS version used: ${status.tlsVersionUsed}`); 82 83 if (this.stopped) { 84 info("handshake done callback stopped - bailing"); 85 return; 86 } 87 88 let callback = new InputStreamCallback(this.output); 89 this.callbacks.push(callback); 90 this.input.asyncWait(callback, 0, 0, Services.tm.currentThread); 91 } 92 93 stop() { 94 this.stopped = true; 95 this.input.close(); 96 this.output.close(); 97 this.callbacks.forEach(callback => { 98 callback.stop(); 99 }); 100 } 101 } 102 103 class ServerSocketListener { 104 constructor() { 105 this.securityObservers = []; 106 } 107 108 onSocketAccepted(socket, transport) { 109 info("accepted TLS client connection"); 110 let connectionInfo = transport.securityCallbacks.getInterface( 111 Ci.nsITLSServerConnectionInfo 112 ); 113 let input = transport.openInputStream(0, 0, 0); 114 let output = transport.openOutputStream(0, 0, 0); 115 let securityObserver = new TLSServerSecurityObserver(input, output); 116 this.securityObservers.push(securityObserver); 117 connectionInfo.setSecurityObserver(securityObserver); 118 } 119 120 // For some reason we get input stream callback events after we've stopped 121 // listening, so this ensures we just drop those events. 122 onStopListening() { 123 info("onStopListening"); 124 this.securityObservers.forEach(observer => { 125 observer.stop(); 126 }); 127 } 128 } 129 130 function startServer( 131 minServerVersion = Ci.nsITLSClientStatus.TLS_VERSION_1_2, 132 maxServerVersion = Ci.nsITLSClientStatus.TLS_VERSION_1_3 133 ) { 134 let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance( 135 Ci.nsITLSServerSocket 136 ); 137 tlsServer.init(-1, true, -1); 138 tlsServer.serverCert = getTestServerCertificate(); 139 tlsServer.setVersionRange(minServerVersion, maxServerVersion); 140 tlsServer.setSessionTickets(false); 141 tlsServer.asyncListen(new ServerSocketListener()); 142 storeCertOverride(tlsServer.port, tlsServer.serverCert); 143 return tlsServer; 144 } 145 146 const hostname = "example.com"; 147 148 function storeCertOverride(port, cert) { 149 let certOverrideService = Cc[ 150 "@mozilla.org/security/certoverride;1" 151 ].getService(Ci.nsICertOverrideService); 152 certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true); 153 } 154 155 function startClient(port, useGREASE, beConservative) { 156 HandshakeTelemetryHelpers.resetHistograms(); 157 let flavors = ["", "_FIRST_TRY"]; 158 let nonflavors = ["_ECH"]; 159 160 if (useGREASE) { 161 Services.prefs.setIntPref("security.tls.ech.grease_probability", 100); 162 } else { 163 Services.prefs.setIntPref("security.tls.ech.grease_probability", 0); 164 } 165 166 let req = new XMLHttpRequest(); 167 req.open("GET", `https://${hostname}:${port}`); 168 169 if (beConservative) { 170 // We don't have a way to set DONT_TRY_ECH at the moment. 171 let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal); 172 internalChannel.beConservative = beConservative; 173 flavors.push("_CONSERVATIVE"); 174 } else { 175 nonflavors.push("_CONSERVATIVE"); 176 } 177 178 //GREASE is only used if enabled and not in conservative mode. 179 if (useGREASE && !beConservative) { 180 flavors.push("_ECH_GREASE"); 181 } else { 182 nonflavors.push("_ECH_GREASE"); 183 } 184 185 return new Promise(resolve => { 186 req.onload = () => { 187 equal(req.responseText, "OK", "response text should be 'OK'"); 188 189 // Only check telemetry if network process is disabled. 190 if (!mozinfo.socketprocess_networking) { 191 HandshakeTelemetryHelpers.checkSuccess(flavors); 192 HandshakeTelemetryHelpers.checkEmpty(nonflavors); 193 } 194 195 resolve(); 196 }; 197 req.onerror = () => { 198 ok(false, `should not have gotten an error`); 199 resolve(); 200 }; 201 202 req.send(); 203 }); 204 } 205 206 function setup() { 207 Services.prefs.setIntPref("security.tls.version.max", 4); 208 Services.prefs.setCharPref("network.dns.localDomains", hostname); 209 Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0); 210 } 211 setup(); 212 213 add_task(async function GreaseYConservativeN() { 214 // First run a server that accepts TLS 1.2 and 1.3. A conservative client 215 // should succeed in connecting. 216 let server = startServer(); 217 218 await startClient( 219 server.port, 220 true /*be conservative*/, 221 false /*should succeed*/ 222 ); 223 server.close(); 224 }); 225 226 add_task(async function GreaseNConservativeY() { 227 // First run a server that accepts TLS 1.2 and 1.3. A conservative client 228 // should succeed in connecting. 229 let server = startServer(); 230 231 await startClient( 232 server.port, 233 false /*be conservative*/, 234 true /*should succeed*/ 235 ); 236 server.close(); 237 }); 238 239 add_task(async function GreaseYConservativeY() { 240 // First run a server that accepts TLS 1.2 and 1.3. A conservative client 241 // should succeed in connecting. 242 let server = startServer(); 243 244 await startClient( 245 server.port, 246 true /*be conservative*/, 247 true /*should succeed*/ 248 ); 249 server.close(); 250 }); 251 252 add_task(async function GreaseNConservativeN() { 253 // First run a server that accepts TLS 1.2 and 1.3. A conservative client 254 // should succeed in connecting. 255 let server = startServer(); 256 257 await startClient( 258 server.port, 259 false /*be conservative*/, 260 false /*should succeed*/ 261 ); 262 server.close(); 263 }); 264 265 registerCleanupFunction(function () { 266 Services.prefs.clearUserPref("security.tls.version.max"); 267 Services.prefs.clearUserPref("network.dns.localDomains"); 268 Services.prefs.clearUserPref("security.tls.ech.grease_probability"); 269 Services.prefs.clearUserPref("network.http.speculative-parallel-limit"); 270 });