test_ntlm_proxy_and_web_auth.js (13603B)
1 // Unit tests for a NTLM authenticated proxy, proxying for a NTLM authenticated 2 // web server. 3 // 4 // Currently the tests do not determine whether the Authentication dialogs have 5 // been displayed. 6 // 7 8 "use strict"; 9 10 const { HttpServer } = ChromeUtils.importESModule( 11 "resource://testing-common/httpd.sys.mjs" 12 ); 13 14 ChromeUtils.defineLazyGetter(this, "URL", function () { 15 return "http://localhost:" + httpserver.identity.primaryPort; 16 }); 17 18 function AuthPrompt() {} 19 20 AuthPrompt.prototype = { 21 user: "guest", 22 pass: "guest", 23 24 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), 25 26 promptAuth: function ap_promptAuth(channel, level, authInfo) { 27 authInfo.username = this.user; 28 authInfo.password = this.pass; 29 30 return true; 31 }, 32 33 asyncPromptAuth: function ap_async() { 34 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 35 }, 36 }; 37 38 function Requestor() {} 39 40 Requestor.prototype = { 41 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 42 43 getInterface: function requestor_gi(iid) { 44 if (iid.equals(Ci.nsIAuthPrompt2)) { 45 // Allow the prompt to store state by caching it here 46 if (!this.prompt) { 47 this.prompt = new AuthPrompt(); 48 } 49 return this.prompt; 50 } 51 52 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 53 }, 54 55 prompt: null, 56 }; 57 58 function makeChan(url, loadingUrl) { 59 var principal = Services.scriptSecurityManager.createContentPrincipal( 60 Services.io.newURI(loadingUrl), 61 {} 62 ); 63 return NetUtil.newChannel({ 64 uri: url, 65 loadingPrincipal: principal, 66 securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, 67 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, 68 }); 69 } 70 71 function TestListener(resolve) { 72 this.resolve = resolve; 73 } 74 TestListener.prototype.onStartRequest = function (request) { 75 // Need to do the instanceof to allow request.responseStatus 76 // to be read. 77 if (!(request instanceof Ci.nsIHttpChannel)) { 78 dump("Expecting an HTTP channel"); 79 } 80 81 Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code"); 82 }; 83 TestListener.prototype.onStopRequest = function () { 84 Assert.equal(expectedRequests, requestsMade, "Number of requests made "); 85 86 this.resolve(); 87 }; 88 TestListener.prototype.onDataAvaiable = function ( 89 request, 90 context, 91 stream, 92 offset, 93 count 94 ) { 95 read_stream(stream, count); 96 }; 97 98 // NTLM Messages, for the received type 1 and 3 messages only check that they 99 // are of the expected type. 100 const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA"; 101 const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA"; 102 const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA"; 103 const NTLM_PREFIX_LEN = 21; 104 105 const NTLM_CHALLENGE = 106 NTLM_TYPE2_PREFIX + 107 "DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" + 108 "ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" + 109 "BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" + 110 "lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="; 111 112 const PROXY_CHALLENGE = 113 NTLM_TYPE2_PREFIX + 114 "DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" + 115 "BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" + 116 "TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" + 117 "YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA"; 118 119 // Proxy and Web server responses for the happy path scenario. 120 // i.e. successful proxy auth and successful web server auth 121 // 122 function authHandler(metadata, response) { 123 let authorization; 124 let authPrefix; 125 switch (requestsMade) { 126 case 0: 127 // Proxy - First request to the Proxy resppond with a 407 to start auth 128 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 129 response.setHeader("Proxy-Authenticate", "NTLM", false); 130 break; 131 case 1: 132 // Proxy - Expecting a type 1 negotiate message from the client 133 authorization = metadata.getHeader("Proxy-Authorization"); 134 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 135 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 136 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 137 response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false); 138 break; 139 case 2: 140 // Proxy - Expecting a type 3 Authenticate message from the client 141 // Will respond with a 401 to start web server auth sequence 142 authorization = metadata.getHeader("Proxy-Authorization"); 143 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 144 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message"); 145 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 146 response.setHeader("WWW-Authenticate", "NTLM", false); 147 break; 148 case 3: 149 // Web Server - Expecting a type 1 negotiate message from the client 150 authorization = metadata.getHeader("Authorization"); 151 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 152 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 153 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 154 response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false); 155 break; 156 case 4: 157 // Web Server - Expecting a type 3 Authenticate message from the client 158 authorization = metadata.getHeader("Authorization"); 159 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 160 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message"); 161 response.setStatusLine(metadata.httpVersion, 200, "Successful"); 162 break; 163 default: 164 // We should be authenticated and further requests are permitted 165 authorization = metadata.getHeader("Authorization"); 166 authorization = metadata.getHeader("Proxy-Authorization"); 167 Assert.isnull(authorization); 168 response.setStatusLine(metadata.httpVersion, 200, "Successful"); 169 } 170 requestsMade++; 171 } 172 173 // Proxy responses simulating an invalid proxy password 174 // Note: that the connection should not be reused after the 175 // proxy auth fails. 176 // 177 function authHandlerInvalidProxyPassword(metadata, response) { 178 let authorization; 179 let authPrefix; 180 switch (requestsMade) { 181 case 0: 182 // Proxy - First request respond with a 407 to initiate auth sequence 183 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 184 response.setHeader("Proxy-Authenticate", "NTLM", false); 185 break; 186 case 1: 187 // Proxy - Expecting a type 1 negotiate message from the client 188 authorization = metadata.getHeader("Proxy-Authorization"); 189 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 190 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 191 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 192 response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false); 193 break; 194 case 2: 195 // Proxy - Expecting a type 3 Authenticate message from the client 196 // Respond with a 407 to indicate invalid credentials 197 // 198 authorization = metadata.getHeader("Proxy-Authorization"); 199 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 200 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message"); 201 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 202 response.setHeader("Proxy-Authenticate", "NTLM", false); 203 break; 204 default: 205 // Strictly speaking the connection should not be reused at this point 206 // and reaching here should be an error, but have commented out for now 207 //dump( "ERROR: NTLM Proxy Authentication, connection should not be reused"); 208 //Assert.fail(); 209 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 210 } 211 requestsMade++; 212 } 213 214 // Proxy and web server responses simulating a successful Proxy auth 215 // and a failed web server auth 216 // Note: the connection should not be reused once the password failure is 217 // detected 218 function authHandlerInvalidWebPassword(metadata, response) { 219 let authorization; 220 let authPrefix; 221 switch (requestsMade) { 222 case 0: 223 // Proxy - First request return a 407 to start Proxy auth 224 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 225 response.setHeader("Proxy-Authenticate", "NTLM", false); 226 break; 227 case 1: 228 // Proxy - Expecting a type 1 negotiate message from the client 229 authorization = metadata.getHeader("Proxy-Authorization"); 230 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 231 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 232 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized"); 233 response.setHeader("Proxy-Authenticate", NTLM_CHALLENGE, false); 234 break; 235 case 2: 236 // Proxy - Expecting a type 3 Authenticate message from the client 237 // Responds with a 401 to start web server auth 238 authorization = metadata.getHeader("Proxy-Authorization"); 239 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 240 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message"); 241 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 242 response.setHeader("WWW-Authenticate", "NTLM", false); 243 break; 244 case 3: 245 // Web Server - Expecting a type 1 negotiate message from the client 246 authorization = metadata.getHeader("Authorization"); 247 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 248 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 249 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 250 response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false); 251 break; 252 case 4: 253 // Web Server - Expecting a type 3 Authenticate message from the client 254 // Respond with a 401 to restart the auth sequence. 255 authorization = metadata.getHeader("Authorization"); 256 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 257 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 1 message"); 258 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 259 break; 260 default: 261 // We should not get called past step 4 262 Assert.ok( 263 false, 264 "ERROR: NTLM Auth failed connection should not be reused" 265 ); 266 } 267 requestsMade++; 268 } 269 270 // Tests to run test_bad_proxy_pass and test_bad_web_pass are split into two stages 271 // so that once we determine how detect password dialog displays we can check 272 // that the are displayed correctly, i.e. proxy password should not be prompted 273 // for when retrying the web server password 274 275 var httpserver = null; 276 function setup() { 277 httpserver = new HttpServer(); 278 httpserver.start(-1); 279 280 Services.prefs.setCharPref("network.proxy.http", "localhost"); 281 Services.prefs.setIntPref( 282 "network.proxy.http_port", 283 httpserver.identity.primaryPort 284 ); 285 Services.prefs.setCharPref("network.proxy.no_proxies_on", ""); 286 Services.prefs.setIntPref("network.proxy.type", 1); 287 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); 288 289 registerCleanupFunction(async () => { 290 Services.prefs.clearUserPref("network.proxy.http"); 291 Services.prefs.clearUserPref("network.proxy.http_port"); 292 Services.prefs.clearUserPref("network.proxy.no_proxies_on"); 293 Services.prefs.clearUserPref("network.proxy.type"); 294 Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost"); 295 296 await httpserver.stop(); 297 }); 298 } 299 setup(); 300 301 var expectedRequests = 0; // Number of HTTP requests that are expected 302 var requestsMade = 0; // The number of requests that were made 303 var expectedResponse = 0; // The response code 304 // Note that any test failures in the HTTP handler 305 // will manifest as a 500 response code 306 307 // Common test setup 308 // Parameters: 309 // path - path component of the URL 310 // handler - http handler function for the httpserver 311 // requests - expected number oh http requests 312 // response - expected http response code 313 // clearCache - clear the authentication cache before running the test 314 function setupTest(path, handler, requests, response, clearCache) { 315 requestsMade = 0; 316 expectedRequests = requests; 317 expectedResponse = response; 318 319 // clear the auth cache if requested 320 if (clearCache) { 321 dump("Clearing auth cache"); 322 Cc["@mozilla.org/network/http-auth-manager;1"] 323 .getService(Ci.nsIHttpAuthManager) 324 .clearAll(); 325 } 326 327 return new Promise(resolve => { 328 var chan = makeChan(URL + path, URL); 329 httpserver.registerPathHandler(path, handler); 330 chan.notificationCallbacks = new Requestor(); 331 chan.asyncOpen(new TestListener(resolve)); 332 }); 333 } 334 335 // Happy code path 336 // Succesful proxy and web server auth. 337 add_task(async function test_happy_path() { 338 dump("RUNNING TEST: test_happy_path"); 339 await setupTest("/auth", authHandler, 5, 200, 1); 340 }); 341 342 // Failed proxy authentication 343 add_task(async function test_bad_proxy_pass_stage01() { 344 dump("RUNNING TEST: test_bad_proxy_pass_stage01"); 345 await setupTest("/auth", authHandlerInvalidProxyPassword, 4, 407, 1); 346 }); 347 // Successful logon after failed proxy auth 348 add_task(async function test_bad_proxy_pass_stage02() { 349 dump("RUNNING TEST: test_bad_proxy_pass_stage02"); 350 await setupTest("/auth", authHandler, 5, 200, 0); 351 }); 352 353 // successful proxy logon, unsuccessful web server sign on 354 add_task(async function test_bad_web_pass_stage01() { 355 dump("RUNNING TEST: test_bad_web_pass_stage01"); 356 await setupTest("/auth", authHandlerInvalidWebPassword, 5, 401, 1); 357 }); 358 // successful logon after failed web server auth. 359 add_task(async function test_bad_web_pass_stage02() { 360 dump("RUNNING TEST: test_bad_web_pass_stage02"); 361 await setupTest("/auth", authHandler, 5, 200, 0); 362 });