gecko/services/sync/tests/unit/test_jpakeclient.js
2010-12-09 22:53:08 -08:00

370 lines
10 KiB
JavaScript

Cu.import("resource://services-sync/log4moz.js");
Cu.import("resource://services-sync/jpakeclient.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/auth.js");
Cu.import("resource://services-sync/util.js");
const JPAKE_LENGTH_SECRET = 8;
const JPAKE_LENGTH_CLIENTID = 256;
/*
* Simple server.
*/
function check_headers(request) {
// There shouldn't be any Basic auth
do_check_false(request.hasHeader("Authorization"));
// Ensure key exchange ID is set and the right length
do_check_true(request.hasHeader("X-KeyExchange-Id"));
do_check_eq(request.getHeader("X-KeyExchange-Id").length,
JPAKE_LENGTH_CLIENTID);
}
function new_channel() {
// Create a new channel and register it with the server.
let cid = Math.floor(Math.random() * 10000);
while (channels[cid])
cid = Math.floor(Math.random() * 10000);
let channel = channels[cid] = new ServerChannel();
server.registerPathHandler("/" + cid, channel.handler());
return cid;
}
let server;
let channels = {}; // Map channel -> ServerChannel object
function server_new_channel(request, response) {
check_headers(request);
let cid = new_channel();
let body = JSON.stringify("" + cid);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
}
let error_report;
function server_report(request, response) {
check_headers(request);
if (request.hasHeader("X-KeyExchange-Log"))
error_report = request.getHeader("X-KeyExchange-Log");
if (request.hasHeader("X-KeyExchange-Cid")) {
let cid = request.getHeader("X-KeyExchange-Cid");
let channel = channels[cid];
if (channel)
channel.clear();
}
response.setStatusLine(request.httpVersion, 200, "OK");
}
function ServerChannel() {
this.data = "{}";
this.getCount = 0;
}
ServerChannel.prototype = {
GET: function GET(request, response) {
if (!this.data) {
response.setStatusLine(request.httpVersion, 404, "Not Found");
return;
}
if (request.hasHeader("If-None-Match")) {
let etag = request.getHeader("If-None-Match");
if (etag == this._etag) {
response.setStatusLine(request.httpVersion, 304, "Not Modified");
return;
}
}
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(this.data, this.data.length);
// Automatically clear the channel after 6 successful GETs.
this.getCount += 1;
if (this.getCount == 6)
this.clear();
},
PUT: function PUT(request, response) {
this.data = readBytesFromInputStream(request.bodyInputStream);
this._etag = '"' + Utils.sha1(this.data) + '"';
response.setHeader("ETag", this._etag);
response.setStatusLine(request.httpVersion, 200, "OK");
},
clear: function clear() {
delete this.data;
},
handler: function handler() {
let self = this;
return function(request, response) {
check_headers(request);
let method = self[request.method];
return method.apply(self, arguments);
};
}
};
const DATA = {"msg": "eggstreamly sekrit"};
const POLLINTERVAL = 50;
function run_test() {
if (DISABLE_TESTS_BUG_618233)
return;
Svc.Prefs.set("jpake.serverURL", "http://localhost:8080/");
Svc.Prefs.set("jpake.pollInterval", POLLINTERVAL);
Svc.Prefs.set("jpake.maxTries", 5);
Svc.Prefs.set("jpake.firstMsgMaxTries", 5);
// Ensure clean up
Svc.Obs.add("profile-before-change", function() {
Svc.Prefs.resetBranch("");
});
// Ensure PSM is initialized.
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
// Simulate Sync setup with a default authenticator in place. We
// want to make sure the J-PAKE requests don't include those data.
Auth.defaultAuthenticator = new BasicAuthenticator(
new Identity("Some Realm", "johndoe"));
server = httpd_setup({"/new_channel": server_new_channel,
"/report": server_report});
function tearDown() {
server.stop(do_test_finished);
}
initTestLogging("Trace");
do_test_pending();
Utils.asyncChain(test_success_receiveNoPIN,
test_firstMsgMaxTries,
test_wrongPIN,
test_abort_receiver,
test_abort_sender,
test_wrongmessage,
test_error_channel,
test_error_network,
tearDown
)();
}
function test_success_receiveNoPIN(next) {
_("Test a successful exchange started by receiveNoPIN().");
let snd = new JPAKEClient({
displayPIN: function displayPIN() {
do_throw("displayPIN shouldn't have been called!");
},
onAbort: function onAbort(error) {
do_throw("Shouldn't have aborted!" + error);
},
onComplete: function onComplete() {}
});
let rec = new JPAKEClient({
displayPIN: function displayPIN(pin) {
_("Received PIN " + pin + ". Entering it in the other computer...");
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
Utils.delay(function() { snd.sendWithPIN(pin, DATA); }, 0,
this, "_timer");
},
onAbort: function onAbort(error) {
do_throw("Shouldn't have aborted! " + error);
},
onComplete: function onComplete(a) {
// Ensure channel was cleared, no error report.
do_check_eq(channels[this.cid].data, undefined);
do_check_eq(error_report, undefined);
next();
}
});
rec.receiveNoPIN();
}
function test_firstMsgMaxTries(next) {
_("Test abort when sender doesn't upload anything.");
let rec = new JPAKEClient({
displayPIN: function displayPIN(pin) {
_("Received PIN " + pin + ". Doing nothing...");
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_TIMEOUT);
// Ensure channel was cleared, error report was sent.
do_check_eq(channels[this.cid].data, undefined);
do_check_eq(error_report, JPAKE_ERROR_TIMEOUT);
error_report = undefined;
next();
},
onComplete: function onComplete() {
do_throw("Shouldn't have completed! ");
}
});
rec.receiveNoPIN();
}
function test_wrongPIN(next) {
_("Test abort when PINs don't match.");
let snd = new JPAKEClient({
displayPIN: function displayPIN() {
do_throw("displayPIN shouldn't have been called!");
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_KEYMISMATCH);
do_check_eq(error_report, JPAKE_ERROR_KEYMISMATCH);
error_report = undefined;
},
onComplete: function onComplete() {
do_throw("Shouldn't have completed!");
}
});
let rec = new JPAKEClient({
displayPIN: function displayPIN(pin) {
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
let secret = pin.slice(0, JPAKE_LENGTH_SECRET);
secret = [char for each (char in secret)].reverse().join("");
let new_pin = secret + this.cid;
_("Received PIN " + pin + ", but I'm entering " + new_pin);
Utils.delay(function() { snd.sendWithPIN(new_pin, DATA); }, 0,
this, "_timer");
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_NODATA);
// Ensure channel was cleared.
do_check_eq(channels[this.cid].data, undefined);
next();
},
onComplete: function onComplete() {
do_throw("Shouldn't have completed! ");
}
});
rec.receiveNoPIN();
}
function test_abort_receiver(next) {
_("Test user abort on receiving side.");
let rec = new JPAKEClient({
onComplete: function onComplete(data) {
do_throw("onComplete shouldn't be called.");
},
onAbort: function onAbort(error) {
// Manual abort = no error
do_check_eq(error, undefined);
// Ensure channel was cleared, no error report.
do_check_eq(channels[this.cid].data, undefined);
do_check_eq(error_report, undefined);
next();
},
displayPIN: function displayPIN(pin) {
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
Utils.delay(function() { rec.abort(); }, 0, this, "_timer");
}
});
rec.receiveNoPIN();
}
function test_abort_sender(next) {
_("Test user abort on sending side.");
let snd = new JPAKEClient({
displayPIN: function displayPIN() {
do_throw("displayPIN shouldn't have been called!");
},
onAbort: function onAbort(error) {
// Manual abort == no error.
do_check_eq(error, undefined);
},
onComplete: function onComplete() {
do_throw("Shouldn't have completed!");
}
});
let rec = new JPAKEClient({
onComplete: function onComplete(data) {
do_throw("onComplete shouldn't be called.");
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_NODATA);
// Ensure channel was cleared, no error report.
do_check_eq(channels[this.cid].data, undefined);
do_check_eq(error_report, undefined);
next();
},
displayPIN: function displayPIN(pin) {
_("Received PIN " + pin + ". Entering it in the other computer...");
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
Utils.delay(function() { snd.sendWithPIN(pin, DATA); }, 0,
this, "_timer");
Utils.delay(function() { snd.abort(); }, POLLINTERVAL,
this, "_abortTimer");
}
});
rec.receiveNoPIN();
}
function test_wrongmessage(next) {
let cid = new_channel();
channels[cid].data = JSON.stringify({type: "receiver2", payload: {}});
let snd = new JPAKEClient({
onComplete: function onComplete(data) {
do_throw("onComplete shouldn't be called.");
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_WRONGMESSAGE);
next();
}
});
snd.sendWithPIN("01234567" + cid, DATA);
}
function test_error_channel(next) {
Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
let rec = new JPAKEClient({
onComplete: function onComplete(data) {
do_throw("onComplete shouldn't be called.");
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_CHANNEL);
Svc.Prefs.reset("jpake.serverURL");
next();
},
displayPIN: function displayPIN(pin) {}
});
rec.receiveNoPIN();
}
function test_error_network(next) {
Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
let snd = new JPAKEClient({
onComplete: function onComplete(data) {
do_throw("onComplete shouldn't be called.");
},
onAbort: function onAbort(error) {
do_check_eq(error, JPAKE_ERROR_NETWORK);
Svc.Prefs.reset("jpake.serverURL");
next();
}
});
snd.sendWithPIN("0123456789ab", DATA);
}