test_logoutAndTeardown.js (5332B)
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 // This test ensures that in-progress https connections are cancelled when the 8 // user logs out of a PKCS#11 token. 9 10 // Get a profile directory and ensure PSM initializes NSS. 11 do_get_profile(); 12 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); 13 14 function getTestServerCertificate() { 15 const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService( 16 Ci.nsIX509CertDB 17 ); 18 const certFile = do_get_file("test_certDB_import/encrypted_with_aes.p12"); 19 certDB.importPKCS12File(certFile, "password"); 20 for (const cert of certDB.getCerts()) { 21 if (cert.commonName == "John Doe") { 22 return cert; 23 } 24 } 25 return null; 26 } 27 28 class InputStreamCallback { 29 constructor(output) { 30 this.output = output; 31 this.stopped = false; 32 } 33 34 onInputStreamReady(stream) { 35 info("input stream ready"); 36 if (this.stopped) { 37 info("input stream callback stopped - bailing"); 38 return; 39 } 40 let available = 0; 41 try { 42 available = stream.available(); 43 } catch (e) { 44 // onInputStreamReady may fire when the stream has been closed. 45 equal( 46 e.result, 47 Cr.NS_BASE_STREAM_CLOSED, 48 "error should be NS_BASE_STREAM_CLOSED" 49 ); 50 } 51 if (available > 0) { 52 let request = NetUtil.readInputStreamToString(stream, available, { 53 charset: "utf8", 54 }); 55 ok( 56 request.startsWith("GET / HTTP/1.1\r\n"), 57 "Should get a simple GET / HTTP/1.1 request" 58 ); 59 let response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; 60 this.output.write(response, response.length); 61 // Keep writing a response until the client disconnects due to the 62 // logoutAndTeardown. If the client never disconnects, the test will time 63 // out, indicating a bug. 64 while (true) { 65 this.output.write("a", 1); 66 } 67 } 68 this.output.close(); 69 info("done with input stream ready"); 70 } 71 72 stop() { 73 this.stopped = true; 74 this.output.close(); 75 } 76 } 77 78 class TLSServerSecurityObserver { 79 constructor(input, output) { 80 this.input = input; 81 this.output = output; 82 this.callbacks = []; 83 this.stopped = false; 84 } 85 86 onHandshakeDone(socket, status) { 87 info("TLS handshake done"); 88 info(`TLS version used: ${status.tlsVersionUsed}`); 89 90 if (this.stopped) { 91 info("handshake done callback stopped - bailing"); 92 return; 93 } 94 95 let callback = new InputStreamCallback(this.output); 96 this.callbacks.push(callback); 97 this.input.asyncWait(callback, 0, 0, Services.tm.currentThread); 98 99 // We've set up everything needed for a successful request/response, 100 // but calling logoutAndTeardown should cause the request to be cancelled. 101 Cc["@mozilla.org/security/sdr;1"] 102 .getService(Ci.nsISecretDecoderRing) 103 .logoutAndTeardown(); 104 } 105 106 stop() { 107 this.stopped = true; 108 this.input.close(); 109 this.output.close(); 110 this.callbacks.forEach(callback => { 111 callback.stop(); 112 }); 113 } 114 } 115 116 class ServerSocketListener { 117 constructor() { 118 this.securityObservers = []; 119 } 120 121 onSocketAccepted(socket, transport) { 122 info("accepted TLS client connection"); 123 let connectionInfo = transport.securityCallbacks.getInterface( 124 Ci.nsITLSServerConnectionInfo 125 ); 126 let input = transport.openInputStream(0, 0, 0); 127 let output = transport.openOutputStream(0, 0, 0); 128 let securityObserver = new TLSServerSecurityObserver(input, output); 129 this.securityObservers.push(securityObserver); 130 connectionInfo.setSecurityObserver(securityObserver); 131 } 132 133 // For some reason we get input stream callback events after we've stopped 134 // listening, so this ensures we just drop those events. 135 onStopListening() { 136 info("onStopListening"); 137 this.securityObservers.forEach(observer => { 138 observer.stop(); 139 }); 140 } 141 } 142 143 function getStartedServer(cert) { 144 let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance( 145 Ci.nsITLSServerSocket 146 ); 147 tlsServer.init(-1, true, -1); 148 tlsServer.serverCert = cert; 149 tlsServer.setSessionTickets(false); 150 tlsServer.asyncListen(new ServerSocketListener()); 151 return tlsServer; 152 } 153 154 const hostname = "example.com"; 155 156 function storeCertOverride(port, cert) { 157 let certOverrideService = Cc[ 158 "@mozilla.org/security/certoverride;1" 159 ].getService(Ci.nsICertOverrideService); 160 certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true); 161 } 162 163 function startClient(port) { 164 let req = new XMLHttpRequest(); 165 req.open("GET", `https://${hostname}:${port}`); 166 return new Promise(resolve => { 167 req.onload = () => { 168 ok(false, "should not have gotten load event"); 169 resolve(); 170 }; 171 req.onerror = () => { 172 ok(true, "should have gotten an error"); 173 resolve(); 174 }; 175 176 req.send(); 177 }); 178 } 179 180 add_task(async function () { 181 Services.prefs.setCharPref("network.dns.localDomains", hostname); 182 let cert = getTestServerCertificate(); 183 184 let server = getStartedServer(cert); 185 storeCertOverride(server.port, cert); 186 await startClient(server.port); 187 server.close(); 188 }); 189 190 registerCleanupFunction(function () { 191 Services.prefs.clearUserPref("network.dns.localDomains"); 192 });