tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });