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