services/sync/tests/unit/test_jpakeclient.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 Cu.import("resource://gre/modules/Log.jsm");
     2 Cu.import("resource://services-sync/identity.js");
     3 Cu.import("resource://services-sync/jpakeclient.js");
     4 Cu.import("resource://services-sync/constants.js");
     5 Cu.import("resource://services-sync/util.js");
     6 Cu.import("resource://testing-common/services/sync/utils.js");
     8 const JPAKE_LENGTH_SECRET     = 8;
     9 const JPAKE_LENGTH_CLIENTID   = 256;
    10 const KEYEXCHANGE_VERSION     = 3;
    12 /*
    13  * Simple server.
    14  */
    16 const SERVER_MAX_GETS = 6;
    18 function check_headers(request) {
    19   let stack = Components.stack.caller;
    21   // There shouldn't be any Basic auth
    22   do_check_false(request.hasHeader("Authorization"), stack);
    24   // Ensure key exchange ID is set and the right length
    25   do_check_true(request.hasHeader("X-KeyExchange-Id"), stack);
    26   do_check_eq(request.getHeader("X-KeyExchange-Id").length,
    27               JPAKE_LENGTH_CLIENTID, stack);
    28 }
    30 function new_channel() {
    31   // Create a new channel and register it with the server.
    32   let cid = Math.floor(Math.random() * 10000);
    33   while (channels[cid]) {
    34     cid = Math.floor(Math.random() * 10000);
    35   }
    36   let channel = channels[cid] = new ServerChannel();
    37   server.registerPathHandler("/" + cid, channel.handler());
    38   return cid;
    39 }
    41 let server;
    42 let channels = {};  // Map channel -> ServerChannel object
    43 function server_new_channel(request, response) {
    44   check_headers(request);
    45   let cid = new_channel();
    46   let body = JSON.stringify("" + cid);
    47   response.setStatusLine(request.httpVersion, 200, "OK");
    48   response.bodyOutputStream.write(body, body.length);
    49 }
    51 let error_report;
    52 function server_report(request, response) {
    53   check_headers(request);
    55   if (request.hasHeader("X-KeyExchange-Log")) {
    56     error_report = request.getHeader("X-KeyExchange-Log");
    57   }
    59   if (request.hasHeader("X-KeyExchange-Cid")) {
    60     let cid = request.getHeader("X-KeyExchange-Cid");
    61     let channel = channels[cid];
    62     if (channel) {
    63       channel.clear();
    64     }
    65   }
    67   response.setStatusLine(request.httpVersion, 200, "OK");
    68 }
    70 // Hook for test code.
    71 let hooks = {};
    72 function initHooks() {
    73   hooks.onGET = function onGET(request) {};
    74 }
    75 initHooks();
    77 function ServerChannel() {
    78   this.data = "";
    79   this.etag = "";
    80   this.getCount = 0;
    81 }
    82 ServerChannel.prototype = {
    84   GET: function GET(request, response) {
    85     if (!this.data) {
    86       response.setStatusLine(request.httpVersion, 404, "Not Found");
    87       return;
    88     }
    90     if (request.hasHeader("If-None-Match")) {
    91       let etag = request.getHeader("If-None-Match");
    92       if (etag == this.etag) {
    93         response.setStatusLine(request.httpVersion, 304, "Not Modified");
    94         hooks.onGET(request);
    95         return;
    96       }
    97     }
    98     response.setHeader("ETag", this.etag);
    99     response.setStatusLine(request.httpVersion, 200, "OK");
   100     response.bodyOutputStream.write(this.data, this.data.length);
   102     // Automatically clear the channel after 6 successful GETs.
   103     this.getCount += 1;
   104     if (this.getCount == SERVER_MAX_GETS) {
   105       this.clear();
   106     }
   107     hooks.onGET(request);
   108   },
   110   PUT: function PUT(request, response) {
   111     if (this.data) {
   112       do_check_true(request.hasHeader("If-Match"));
   113       let etag = request.getHeader("If-Match");
   114       if (etag != this.etag) {
   115         response.setHeader("ETag", this.etag);
   116         response.setStatusLine(request.httpVersion, 412, "Precondition Failed");
   117         return;
   118       }
   119     } else {
   120       do_check_true(request.hasHeader("If-None-Match"));
   121       do_check_eq(request.getHeader("If-None-Match"), "*");
   122     }
   124     this.data = readBytesFromInputStream(request.bodyInputStream);
   125     this.etag = '"' + Utils.sha1(this.data) + '"';
   126     response.setHeader("ETag", this.etag);
   127     response.setStatusLine(request.httpVersion, 200, "OK");
   128   },
   130   clear: function clear() {
   131     delete this.data;
   132   },
   134   handler: function handler() {
   135     let self = this;
   136     return function(request, response) {
   137       check_headers(request);
   138       let method = self[request.method];
   139       return method.apply(self, arguments);
   140     };
   141   }
   143 };
   146 /**
   147  * Controller that throws for everything.
   148  */
   149 let BaseController = {
   150   displayPIN: function displayPIN() {
   151     do_throw("displayPIN() shouldn't have been called!");
   152   },
   153   onPairingStart: function onPairingStart() {
   154     do_throw("onPairingStart shouldn't have been called!");
   155   },
   156   onAbort: function onAbort(error) {
   157     do_throw("Shouldn't have aborted with " + error + "!");
   158   },
   159   onPaired: function onPaired() {
   160     do_throw("onPaired() shouldn't have been called!");
   161   },
   162   onComplete: function onComplete(data) {
   163     do_throw("Shouldn't have completed with " + data + "!");
   164   }
   165 };
   168 const DATA = {"msg": "eggstreamly sekrit"};
   169 const POLLINTERVAL = 50;
   171 function run_test() {
   172   server = httpd_setup({"/new_channel": server_new_channel,
   173                         "/report":      server_report});
   174   Svc.Prefs.set("jpake.serverURL", server.baseURI + "/");
   175   Svc.Prefs.set("jpake.pollInterval", POLLINTERVAL);
   176   Svc.Prefs.set("jpake.maxTries", 2);
   177   Svc.Prefs.set("jpake.firstMsgMaxTries", 5);
   178   Svc.Prefs.set("jpake.lastMsgMaxTries", 5);
   179   // Ensure clean up
   180   Svc.Obs.add("profile-before-change", function() {
   181     Svc.Prefs.resetBranch("");
   182   });
   184   // Ensure PSM is initialized.
   185   Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
   187   // Simulate Sync setup with credentials in place. We want to make
   188   // sure the J-PAKE requests don't include those data.
   189   ensureLegacyIdentityManager();
   190   setBasicCredentials("johndoe", "ilovejane");
   192   initTestLogging("Trace");
   193   Log.repository.getLogger("Sync.JPAKEClient").level = Log.Level.Trace;
   194   Log.repository.getLogger("Common.RESTRequest").level =
   195     Log.Level.Trace;
   196   run_next_test();
   197 }
   200 add_test(function test_success_receiveNoPIN() {
   201   _("Test a successful exchange started by receiveNoPIN().");
   203   let snd = new JPAKEClient({
   204     __proto__: BaseController,
   205     onPaired: function onPaired() {
   206       _("Pairing successful, sending final payload.");
   207       do_check_true(pairingStartCalledOnReceiver);
   208       Utils.nextTick(function() { snd.sendAndComplete(DATA); });
   209     },
   210     onComplete: function onComplete() {}
   211   });
   213   let pairingStartCalledOnReceiver = false;
   214   let rec = new JPAKEClient({
   215     __proto__: BaseController,
   216     displayPIN: function displayPIN(pin) {
   217       _("Received PIN " + pin + ". Entering it in the other computer...");
   218       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   219       Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
   220     },
   221     onPairingStart: function onPairingStart() {
   222       pairingStartCalledOnReceiver = true;
   223     },
   224     onComplete: function onComplete(data) {
   225       do_check_true(Utils.deepEquals(DATA, data));
   226       // Ensure channel was cleared, no error report.
   227       do_check_eq(channels[this.cid].data, undefined);
   228       do_check_eq(error_report, undefined);
   229       run_next_test();
   230     }
   231   });
   232   rec.receiveNoPIN();
   233 });
   236 add_test(function test_firstMsgMaxTries_timeout() {
   237   _("Test abort when sender doesn't upload anything.");
   239   let rec = new JPAKEClient({
   240     __proto__: BaseController,
   241     displayPIN: function displayPIN(pin) {
   242       _("Received PIN " + pin + ". Doing nothing...");
   243       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   244     },
   245     onAbort: function onAbort(error) {
   246       do_check_eq(error, JPAKE_ERROR_TIMEOUT);
   247       // Ensure channel was cleared, error report was sent.
   248       do_check_eq(channels[this.cid].data, undefined);
   249       do_check_eq(error_report, JPAKE_ERROR_TIMEOUT);
   250       error_report = undefined;
   251       run_next_test();
   252     }
   253   });
   254   rec.receiveNoPIN();
   255 });
   258 add_test(function test_firstMsgMaxTries() {
   259   _("Test that receiver can wait longer for the first message.");
   261   let snd = new JPAKEClient({
   262     __proto__: BaseController,
   263     onPaired: function onPaired() {
   264       _("Pairing successful, sending final payload.");
   265       Utils.nextTick(function() { snd.sendAndComplete(DATA); });
   266     },
   267     onComplete: function onComplete() {}
   268   });
   270   let rec = new JPAKEClient({
   271     __proto__: BaseController,
   272     displayPIN: function displayPIN(pin) {
   273       // For the purpose of the tests, the poll interval is 50ms and
   274       // we're polling up to 5 times for the first exchange (as
   275       // opposed to 2 times for most of the other exchanges). So let's
   276       // pretend it took 150ms to enter the PIN on the sender, which should
   277       // require 3 polls.
   278       // Rather than using an imprecise timer, we hook into the channel's
   279       // GET handler to know how long to wait.
   280       _("Received PIN " + pin + ". Waiting for three polls before entering it into sender...");
   281       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   282       let count = 0;
   283       hooks.onGET = function onGET(request) {
   284         if (++count == 3) {
   285           _("Third GET. Triggering pair.");
   286           Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
   287         }
   288       };
   289     },
   290     onPairingStart: function onPairingStart(pin) {},
   291     onComplete: function onComplete(data) {
   292       do_check_true(Utils.deepEquals(DATA, data));
   293       // Ensure channel was cleared, no error report.
   294       do_check_eq(channels[this.cid].data, undefined);
   295       do_check_eq(error_report, undefined);
   297       // Clean up.
   298       initHooks();
   299       run_next_test();
   300     }
   301   });
   302   rec.receiveNoPIN();
   303 });
   306 add_test(function test_lastMsgMaxTries() {
   307   _("Test that receiver can wait longer for the last message.");
   309  let snd = new JPAKEClient({
   310     __proto__: BaseController,
   311     onPaired: function onPaired() {
   312       // For the purpose of the tests, the poll interval is 50ms and
   313       // we're polling up to 5 times for the last exchange (as opposed
   314       // to 2 times for other exchanges). So let's pretend it took
   315       // 150ms to come up with the final payload, which should require
   316       // 3 polls.
   317       // Rather than using an imprecise timer, we hook into the channel's
   318       // GET handler to know how long to wait.
   319       let count = 0;
   320       hooks.onGET = function onGET(request) {
   321         if (++count == 3) {
   322           _("Third GET. Triggering send.");
   323           Utils.nextTick(function() { snd.sendAndComplete(DATA); });
   324         }
   325       };
   326     },
   327     onComplete: function onComplete() {}
   328   });
   330   let rec = new JPAKEClient({
   331     __proto__: BaseController,
   332     displayPIN: function displayPIN(pin) {
   333       _("Received PIN " + pin + ". Entering it in the other computer...");
   334       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   335       Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
   336     },
   337     onPairingStart: function onPairingStart(pin) {},
   338     onComplete: function onComplete(data) {
   339       do_check_true(Utils.deepEquals(DATA, data));
   340       // Ensure channel was cleared, no error report.
   341       do_check_eq(channels[this.cid].data, undefined);
   342       do_check_eq(error_report, undefined);
   344       // Clean up.
   345       initHooks();
   346       run_next_test();
   347     }
   348   });
   350   rec.receiveNoPIN();
   351 });
   354 add_test(function test_wrongPIN() {
   355   _("Test abort when PINs don't match.");
   357   let snd = new JPAKEClient({
   358     __proto__: BaseController,
   359     onAbort: function onAbort(error) {
   360       do_check_eq(error, JPAKE_ERROR_KEYMISMATCH);
   361       do_check_eq(error_report, JPAKE_ERROR_KEYMISMATCH);
   362       error_report = undefined;
   363     }
   364   });
   366   let pairingStartCalledOnReceiver = false;
   367   let rec = new JPAKEClient({
   368     __proto__: BaseController,
   369     displayPIN: function displayPIN(pin) {
   370       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   371       let secret = pin.slice(0, JPAKE_LENGTH_SECRET);
   372       secret = [char for each (char in secret)].reverse().join("");
   373       let new_pin = secret + this.cid;
   374       _("Received PIN " + pin + ", but I'm entering " + new_pin);
   376       Utils.nextTick(function() { snd.pairWithPIN(new_pin, false); });
   377     },
   378     onPairingStart: function onPairingStart() {
   379       pairingStartCalledOnReceiver = true;
   380     },
   381     onAbort: function onAbort(error) {
   382       do_check_true(pairingStartCalledOnReceiver);
   383       do_check_eq(error, JPAKE_ERROR_NODATA);
   384       // Ensure channel was cleared.
   385       do_check_eq(channels[this.cid].data, undefined);
   386       run_next_test();
   387     }
   388   });
   389   rec.receiveNoPIN();
   390 });
   393 add_test(function test_abort_receiver() {
   394   _("Test user abort on receiving side.");
   396   let rec = new JPAKEClient({
   397     __proto__: BaseController,
   398     onAbort: function onAbort(error) {
   399       // Manual abort = userabort.
   400       do_check_eq(error, JPAKE_ERROR_USERABORT);
   401       // Ensure channel was cleared.
   402       do_check_eq(channels[this.cid].data, undefined);
   403       do_check_eq(error_report, JPAKE_ERROR_USERABORT);
   404       error_report = undefined;
   405       run_next_test();
   406     },
   407     displayPIN: function displayPIN(pin) {
   408       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   409       Utils.nextTick(function() { rec.abort(); });
   410     }
   411   });
   412   rec.receiveNoPIN();
   413 });
   416 add_test(function test_abort_sender() {
   417   _("Test user abort on sending side.");
   419   let snd = new JPAKEClient({
   420     __proto__: BaseController,
   421     onAbort: function onAbort(error) {
   422       // Manual abort == userabort.
   423       do_check_eq(error, JPAKE_ERROR_USERABORT);
   424       do_check_eq(error_report, JPAKE_ERROR_USERABORT);
   425       error_report = undefined;
   426     }
   427   });
   429   let rec = new JPAKEClient({
   430     __proto__: BaseController,
   431     onAbort: function onAbort(error) {
   432       do_check_eq(error, JPAKE_ERROR_NODATA);
   433       // Ensure channel was cleared, no error report.
   434       do_check_eq(channels[this.cid].data, undefined);
   435       do_check_eq(error_report, undefined);
   436       initHooks();
   437       run_next_test();
   438     },
   439     displayPIN: function displayPIN(pin) {
   440       _("Received PIN " + pin + ". Entering it in the other computer...");
   441       this.cid = pin.slice(JPAKE_LENGTH_SECRET);
   442       Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
   444       // Abort after the first poll.
   445       let count = 0;
   446       hooks.onGET = function onGET(request) {
   447         if (++count >= 1) {
   448           _("First GET. Aborting.");
   449           Utils.nextTick(function() { snd.abort(); });
   450         }
   451       };
   452     },
   453     onPairingStart: function onPairingStart(pin) {}
   454   });
   455   rec.receiveNoPIN();
   456 });
   459 add_test(function test_wrongmessage() {
   460   let cid = new_channel();
   461   let channel = channels[cid];
   462   channel.data = JSON.stringify({type: "receiver2",
   463                                  version: KEYEXCHANGE_VERSION,
   464                                  payload: {}});
   465   channel.etag = '"fake-etag"';
   466   let snd = new JPAKEClient({
   467     __proto__: BaseController,
   468     onComplete: function onComplete(data) {
   469       do_throw("onComplete shouldn't be called.");
   470     },
   471     onAbort: function onAbort(error) {
   472       do_check_eq(error, JPAKE_ERROR_WRONGMESSAGE);
   473       run_next_test();
   474     }
   475   });
   476   snd.pairWithPIN("01234567" + cid, false);
   477 });
   480 add_test(function test_error_channel() {
   481   let serverURL = Svc.Prefs.get("jpake.serverURL");
   482   Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
   484   let rec = new JPAKEClient({
   485     __proto__: BaseController,
   486     onAbort: function onAbort(error) {
   487       do_check_eq(error, JPAKE_ERROR_CHANNEL);
   488       Svc.Prefs.set("jpake.serverURL", serverURL);
   489       run_next_test();
   490     },
   491     onPairingStart: function onPairingStart(pin) {},
   492     displayPIN: function displayPIN(pin) {}
   493   });
   494   rec.receiveNoPIN();
   495 });
   498 add_test(function test_error_network() {
   499   let serverURL = Svc.Prefs.get("jpake.serverURL");
   500   Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
   502   let snd = new JPAKEClient({
   503     __proto__: BaseController,
   504     onAbort: function onAbort(error) {
   505       do_check_eq(error, JPAKE_ERROR_NETWORK);
   506       Svc.Prefs.set("jpake.serverURL", serverURL);
   507       run_next_test();
   508     }
   509   });
   510   snd.pairWithPIN("0123456789ab", false);
   511 });
   514 add_test(function test_error_server_noETag() {
   515   let cid = new_channel();
   516   let channel = channels[cid];
   517   channel.data = JSON.stringify({type: "receiver1",
   518                                  version: KEYEXCHANGE_VERSION,
   519                                  payload: {}});
   520   // This naughty server doesn't supply ETag (well, it supplies empty one).
   521   channel.etag = "";
   522   let snd = new JPAKEClient({
   523     __proto__: BaseController,
   524     onAbort: function onAbort(error) {
   525       do_check_eq(error, JPAKE_ERROR_SERVER);
   526       run_next_test();
   527     }
   528   });
   529   snd.pairWithPIN("01234567" + cid, false);
   530 });
   533 add_test(function test_error_delayNotSupported() {
   534   let cid = new_channel();
   535   let channel = channels[cid];
   536   channel.data = JSON.stringify({type: "receiver1",
   537                                  version: 2,
   538                                  payload: {}});
   539   channel.etag = '"fake-etag"';
   540   let snd = new JPAKEClient({
   541     __proto__: BaseController,
   542     onAbort: function onAbort(error) {
   543       do_check_eq(error, JPAKE_ERROR_DELAYUNSUPPORTED);
   544       run_next_test();
   545     }
   546   });
   547   snd.pairWithPIN("01234567" + cid, true);
   548 });
   551 add_test(function test_sendAndComplete_notPaired() {
   552   let snd = new JPAKEClient({__proto__: BaseController});
   553   do_check_throws(function () {
   554     snd.sendAndComplete(DATA);
   555   });
   556   run_next_test();
   557 });
   560 add_test(function tearDown() {
   561   server.stop(run_next_test);
   562 });

mercurial