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