tor-browser

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

test_ntlm_authentication.js (7197B)


      1 // This file tests authentication prompt callbacks
      2 // TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
      3 
      4 "use strict";
      5 
      6 const { HttpServer } = ChromeUtils.importESModule(
      7  "resource://testing-common/httpd.sys.mjs"
      8 );
      9 
     10 // Turn off the authentication dialog blocking for this test.
     11 var prefs = Services.prefs;
     12 prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
     13 
     14 ChromeUtils.defineLazyGetter(this, "URL", function () {
     15  return "http://localhost:" + httpserv.identity.primaryPort;
     16 });
     17 
     18 ChromeUtils.defineLazyGetter(this, "PORT", function () {
     19  return httpserv.identity.primaryPort;
     20 });
     21 
     22 const FLAG_RETURN_FALSE = 1 << 0;
     23 const FLAG_WRONG_PASSWORD = 1 << 1;
     24 const FLAG_BOGUS_USER = 1 << 2;
     25 // const FLAG_PREVIOUS_FAILED = 1 << 3;
     26 const CROSS_ORIGIN = 1 << 4;
     27 const FLAG_NO_REALM = 1 << 5;
     28 const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
     29 
     30 function AuthPrompt1(flags) {
     31  this.flags = flags;
     32 }
     33 
     34 AuthPrompt1.prototype = {
     35  user: "guest",
     36  pass: "guest",
     37 
     38  expectedRealm: "secret",
     39 
     40  QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
     41 
     42  prompt: function ap1_prompt() {
     43    do_throw("unexpected prompt call");
     44  },
     45 
     46  promptUsernameAndPassword: function ap1_promptUP(
     47    title,
     48    text,
     49    realm,
     50    savePW,
     51    user,
     52    pw
     53  ) {
     54    if (this.flags & FLAG_NO_REALM) {
     55      // Note that the realm here isn't actually the realm. it's a pw mgr key.
     56      Assert.equal(URL + " (" + this.expectedRealm + ")", realm);
     57    }
     58    if (!(this.flags & CROSS_ORIGIN)) {
     59      if (!text.includes(this.expectedRealm)) {
     60        do_throw("Text must indicate the realm");
     61      }
     62    } else if (text.includes(this.expectedRealm)) {
     63      do_throw("There should not be realm for cross origin");
     64    }
     65    if (!text.includes("localhost")) {
     66      do_throw("Text must indicate the hostname");
     67    }
     68    if (!text.includes(String(PORT))) {
     69      do_throw("Text must indicate the port");
     70    }
     71    if (text.includes("-1")) {
     72      do_throw("Text must contain negative numbers");
     73    }
     74 
     75    if (this.flags & FLAG_RETURN_FALSE) {
     76      return false;
     77    }
     78 
     79    if (this.flags & FLAG_BOGUS_USER) {
     80      this.user = "foo\nbar";
     81    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
     82      this.user = "é";
     83    }
     84 
     85    user.value = this.user;
     86    if (this.flags & FLAG_WRONG_PASSWORD) {
     87      pw.value = this.pass + ".wrong";
     88      // Now clear the flag to avoid an infinite loop
     89      this.flags &= ~FLAG_WRONG_PASSWORD;
     90    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
     91      pw.value = "é";
     92    } else {
     93      pw.value = this.pass;
     94    }
     95    return true;
     96  },
     97 
     98  promptPassword: function ap1_promptPW() {
     99    do_throw("unexpected promptPassword call");
    100  },
    101 };
    102 
    103 function AuthPrompt2(flags) {
    104  this.flags = flags;
    105 }
    106 
    107 AuthPrompt2.prototype = {
    108  user: "guest",
    109  pass: "guest",
    110 
    111  expectedRealm: "secret",
    112 
    113  QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
    114 
    115  promptAuth: function ap2_promptAuth(channel, level, authInfo) {
    116    authInfo.username = this.user;
    117    authInfo.password = this.pass;
    118    return true;
    119  },
    120 
    121  asyncPromptAuth: function ap2_async() {
    122    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    123  },
    124 };
    125 
    126 function Requestor(flags, versions) {
    127  this.flags = flags;
    128  this.versions = versions;
    129 }
    130 
    131 Requestor.prototype = {
    132  QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
    133 
    134  getInterface: function requestor_gi(iid) {
    135    if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
    136      // Allow the prompt to store state by caching it here
    137      if (!this.prompt1) {
    138        this.prompt1 = new AuthPrompt1(this.flags);
    139      }
    140      return this.prompt1;
    141    }
    142    if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
    143      // Allow the prompt to store state by caching it here
    144      if (!this.prompt2) {
    145        this.prompt2 = new AuthPrompt2(this.flags);
    146      }
    147      return this.prompt2;
    148    }
    149 
    150    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    151  },
    152 
    153  prompt1: null,
    154  prompt2: null,
    155 };
    156 
    157 function RealmTestRequestor() {}
    158 
    159 RealmTestRequestor.prototype = {
    160  QueryInterface: ChromeUtils.generateQI([
    161    "nsIInterfaceRequestor",
    162    "nsIAuthPrompt2",
    163  ]),
    164 
    165  getInterface: function realmtest_interface(iid) {
    166    if (iid.equals(Ci.nsIAuthPrompt2)) {
    167      return this;
    168    }
    169 
    170    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    171  },
    172 
    173  promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
    174    Assert.equal(authInfo.realm, '"foo_bar');
    175 
    176    return false;
    177  },
    178 
    179  asyncPromptAuth: function realmtest_async() {
    180    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    181  },
    182 };
    183 
    184 function makeChan(url, loadingUrl) {
    185  var principal = Services.scriptSecurityManager.createContentPrincipal(
    186    Services.io.newURI(loadingUrl),
    187    {}
    188  );
    189  return NetUtil.newChannel({
    190    uri: url,
    191    loadingPrincipal: principal,
    192    securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    193    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
    194  });
    195 }
    196 
    197 // /auth/ntlm/simple
    198 function authNtlmSimple(metadata, response) {
    199  if (!metadata.hasHeader("Authorization")) {
    200    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    201    response.setHeader("WWW-Authenticate", "NTLM", false);
    202    return;
    203  }
    204 
    205  let challenge = metadata.getHeader("Authorization");
    206  if (!challenge.startsWith("NTLM ")) {
    207    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    208    return;
    209  }
    210 
    211  let decoded = atob(challenge.substring(5));
    212  info(decoded);
    213 
    214  if (!decoded.startsWith("NTLMSSP\0")) {
    215    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    216    return;
    217  }
    218 
    219  let isNegotiate = decoded.substring(8).startsWith("\x01\x00\x00\x00");
    220  let isAuthenticate = decoded.substring(8).startsWith("\x03\x00\x00\x00");
    221 
    222  if (isNegotiate) {
    223    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    224    response.setHeader(
    225      "WWW-Authenticate",
    226      "NTLM TlRMTVNTUAACAAAAAAAAAAAoAAABggAAASNFZ4mrze8AAAAAAAAAAAAAAAAAAAAA",
    227      false
    228    );
    229    return;
    230  }
    231 
    232  if (isAuthenticate) {
    233    let body = "OK";
    234    response.bodyOutputStream.write(body, body.length);
    235    return;
    236  }
    237 
    238  // Something else went wrong.
    239  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    240 }
    241 
    242 let httpserv;
    243 add_task(async function test_ntlm() {
    244  Services.prefs.setBoolPref("network.auth.force-generic-ntlm", true);
    245  Services.prefs.setBoolPref("network.auth.force-generic-ntlm-v1", true);
    246 
    247  httpserv = new HttpServer();
    248  httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
    249  httpserv.start(-1);
    250 
    251  registerCleanupFunction(async () => {
    252    Services.prefs.clearUserPref("network.auth.force-generic-ntlm");
    253    Services.prefs.clearUserPref("network.auth.force-generic-ntlm-v1");
    254 
    255    await httpserv.stop();
    256  });
    257 
    258  var chan = makeChan(URL + "/auth/ntlm/simple", URL);
    259 
    260  chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
    261  let [req, buf] = await new Promise(resolve => {
    262    chan.asyncOpen(
    263      new ChannelListener((req1, buf1) => resolve([req1, buf1]), null)
    264    );
    265  });
    266  Assert.ok(buf);
    267  Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
    268 });