tor-browser

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

test_ntlm_web_auth.js (7918B)


      1 // Unit tests for a NTLM authenticated web server.
      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() {}
     71 TestListener.prototype.onStartRequest = function (request) {
     72  // Need to do the instanceof to allow request.responseStatus
     73  // to be read.
     74  if (!(request instanceof Ci.nsIHttpChannel)) {
     75    dump("Expecting an HTTP channel");
     76  }
     77 
     78  Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
     79 };
     80 TestListener.prototype.onStopRequest = function () {
     81  Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
     82 
     83  if (current_test < tests.length - 1) {
     84    current_test++;
     85    tests[current_test]();
     86  } else {
     87    do_test_pending();
     88    httpserver.stop(do_test_finished);
     89  }
     90 
     91  do_test_finished();
     92 };
     93 TestListener.prototype.onDataAvaiable = function (
     94  request,
     95  context,
     96  stream,
     97  offset,
     98  count
     99 ) {
    100  read_stream(stream, count);
    101 };
    102 
    103 // NTLM Messages, for the received type 1 and 3 messages only check that they
    104 // are of the expected type.
    105 const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA";
    106 const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA";
    107 const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA";
    108 const NTLM_PREFIX_LEN = 21;
    109 
    110 const NTLM_CHALLENGE =
    111  NTLM_TYPE2_PREFIX +
    112  "DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" +
    113  "ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" +
    114  "BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" +
    115  "lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
    116 
    117 // Web server responses for the happy path scenario.
    118 // i.e. successful web server auth
    119 //
    120 function successfulAuth(metadata, response) {
    121  let authorization;
    122  let authPrefix;
    123  switch (requestsMade) {
    124    case 0:
    125      // Web Server - Initial request
    126      // Will respond with a 401 to start web server auth sequence
    127      response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    128      response.setHeader("WWW-Authenticate", "NTLM", false);
    129      break;
    130    case 1:
    131      // Web Server - Expecting a type 1 negotiate message from the client
    132      authorization = metadata.getHeader("Authorization");
    133      authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
    134      Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
    135      response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    136      response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
    137      break;
    138    case 2:
    139      // Web Server - Expecting a type 3 Authenticate message from the client
    140      authorization = metadata.getHeader("Authorization");
    141      authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
    142      Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
    143      response.setStatusLine(metadata.httpVersion, 200, "Successful");
    144      break;
    145    default:
    146      // We should be authenticated and further requests are permitted
    147      authorization = metadata.getHeader("Authorization");
    148      Assert.isnull(authorization);
    149      response.setStatusLine(metadata.httpVersion, 200, "Successful");
    150  }
    151  requestsMade++;
    152 }
    153 
    154 // web server responses simulating an unsuccessful web server auth
    155 function failedAuth(metadata, response) {
    156  let authorization;
    157  let authPrefix;
    158  switch (requestsMade) {
    159    case 0:
    160      // Web Server - First request return a 401 to start auth sequence
    161      response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    162      response.setHeader("WWW-Authenticate", "NTLM", false);
    163      break;
    164    case 1:
    165      // Web Server - Expecting a type 1 negotiate message from the client
    166      authorization = metadata.getHeader("Authorization");
    167      authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
    168      Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
    169      response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    170      response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
    171      break;
    172    case 2:
    173      // Web Server - Expecting a type 3 Authenticate message from the client
    174      // Respond with a 401 to restart the auth sequence.
    175      authorization = metadata.getHeader("Authorization");
    176      authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
    177      Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 1 message");
    178      response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    179      break;
    180    default:
    181      // We should not get called past step 2
    182      // Strictly speaking the connection should not be used again
    183      // commented out for testing
    184      // dump( "ERROR: NTLM Auth failed connection should not be reused");
    185      //Assert.fail();
    186      response.setHeader("WWW-Authenticate", "NTLM", false);
    187  }
    188  requestsMade++;
    189 }
    190 
    191 var tests = [test_happy_path, test_failed_auth];
    192 var current_test = 0;
    193 
    194 var httpserver = null;
    195 function run_test() {
    196  httpserver = new HttpServer();
    197  httpserver.start(-1);
    198 
    199  tests[0]();
    200 }
    201 
    202 var expectedRequests = 0; // Number of HTTP requests that are expected
    203 var requestsMade = 0; // The number of requests that were made
    204 var expectedResponse = 0; // The response code
    205 // Note that any test failures in the HTTP handler
    206 // will manifest as a 500 response code
    207 
    208 // Common test setup
    209 // Parameters:
    210 //    path       - path component of the URL
    211 //    handler    - http handler function for the httpserver
    212 //    requests   - expected number oh http requests
    213 //    response   - expected http response code
    214 //    clearCache - clear the authentication cache before running the test
    215 function setupTest(path, handler, requests, response, clearCache) {
    216  requestsMade = 0;
    217  expectedRequests = requests;
    218  expectedResponse = response;
    219 
    220  // clear the auth cache if requested
    221  if (clearCache) {
    222    dump("Clearing auth cache");
    223    Cc["@mozilla.org/network/http-auth-manager;1"]
    224      .getService(Ci.nsIHttpAuthManager)
    225      .clearAll();
    226  }
    227 
    228  var chan = makeChan(URL + path, URL);
    229  httpserver.registerPathHandler(path, handler);
    230  chan.notificationCallbacks = new Requestor();
    231  chan.asyncOpen(new TestListener());
    232 
    233  return chan;
    234 }
    235 
    236 // Happy code path
    237 // Succesful web server auth.
    238 function test_happy_path() {
    239  dump("RUNNING TEST: test_happy_path");
    240  setupTest("/auth", successfulAuth, 3, 200, 1);
    241 
    242  do_test_pending();
    243 }
    244 
    245 // Unsuccessful web server sign on
    246 function test_failed_auth() {
    247  dump("RUNNING TEST: test_failed_auth");
    248  setupTest("/auth", failedAuth, 3, 401, 1);
    249 
    250  do_test_pending();
    251 }