tor-browser

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

test_ntlm_proxy_auth.js (13466B)


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