зеркало из https://github.com/mozilla/pjs.git
Bug 689428 - Part 1: Implement KeyExchange v3 in JPAKEClient. r=rnewman
This commit is contained in:
Родитель
018072a7c4
Коммит
f0713e0255
|
@ -185,6 +185,7 @@ JPAKE_ERROR_NODATA: "jpake.error.nodata",
|
||||||
JPAKE_ERROR_KEYMISMATCH: "jpake.error.keymismatch",
|
JPAKE_ERROR_KEYMISMATCH: "jpake.error.keymismatch",
|
||||||
JPAKE_ERROR_WRONGMESSAGE: "jpake.error.wrongmessage",
|
JPAKE_ERROR_WRONGMESSAGE: "jpake.error.wrongmessage",
|
||||||
JPAKE_ERROR_USERABORT: "jpake.error.userabort",
|
JPAKE_ERROR_USERABORT: "jpake.error.userabort",
|
||||||
|
JPAKE_ERROR_DELAYUNSUPPORTED: "jpake.error.delayunsupported",
|
||||||
|
|
||||||
// info types for Service.getStorageInfo
|
// info types for Service.getStorageInfo
|
||||||
INFO_COLLECTIONS: "collections",
|
INFO_COLLECTIONS: "collections",
|
||||||
|
|
|
@ -48,6 +48,8 @@ Cu.import("resource://services-sync/util.js");
|
||||||
const EXPORTED_SYMBOLS = ["JPAKEClient"];
|
const EXPORTED_SYMBOLS = ["JPAKEClient"];
|
||||||
|
|
||||||
const REQUEST_TIMEOUT = 60; // 1 minute
|
const REQUEST_TIMEOUT = 60; // 1 minute
|
||||||
|
const KEYEXCHANGE_VERSION = 3;
|
||||||
|
|
||||||
const JPAKE_SIGNERID_SENDER = "sender";
|
const JPAKE_SIGNERID_SENDER = "sender";
|
||||||
const JPAKE_SIGNERID_RECEIVER = "receiver";
|
const JPAKE_SIGNERID_RECEIVER = "receiver";
|
||||||
const JPAKE_LENGTH_SECRET = 8;
|
const JPAKE_LENGTH_SECRET = 8;
|
||||||
|
@ -55,50 +57,63 @@ const JPAKE_LENGTH_CLIENTID = 256;
|
||||||
const JPAKE_VERIFY_VALUE = "0123456789ABCDEF";
|
const JPAKE_VERIFY_VALUE = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Client to exchange encrypted data using the J-PAKE algorithm.
|
* Client to exchange encrypted data using the J-PAKE algorithm.
|
||||||
* The exchange between two clients of this type looks like this:
|
* The exchange between two clients of this type looks like this:
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Client A Server Client B
|
* Mobile Server Desktop
|
||||||
* ==================================================================
|
* ===================================================================
|
||||||
* |
|
* |
|
||||||
* retrieve channel <---------------|
|
* retrieve channel <---------------|
|
||||||
* generate random secret |
|
* generate random secret |
|
||||||
* show PIN = secret + channel | ask user for PIN
|
* show PIN = secret + channel | ask user for PIN
|
||||||
* upload A's message 1 ----------->|
|
* upload Mobile's message 1 ------>|
|
||||||
* |--------> retrieve A's message 1
|
* |----> retrieve Mobile's message 1
|
||||||
* |<---------- upload B's message 1
|
* |<----- upload Desktop's message 1
|
||||||
* retrieve B's message 1 <---------|
|
* retrieve Desktop's message 1 <---|
|
||||||
* upload A's message 2 ----------->|
|
* upload Mobile's message 2 ------>|
|
||||||
* |--------> retrieve A's message 2
|
* |----> retrieve Mobile's message 2
|
||||||
* | compute key
|
* | compute key
|
||||||
* |<---------- upload B's message 2
|
* |<----- upload Desktop's message 2
|
||||||
* retrieve B's message 2 <---------|
|
* retrieve Desktop's message 2 <---|
|
||||||
* compute key |
|
* compute key |
|
||||||
* upload sha256d(key) ------------>|
|
* encrypt known value ------------>|
|
||||||
* |---------> retrieve sha256d(key)
|
* |-------> retrieve encrypted value
|
||||||
* | verify against own key
|
* | verify against local known value
|
||||||
* | encrypt data
|
*
|
||||||
* |<------------------- upload data
|
* At this point Desktop knows whether the PIN was entered correctly.
|
||||||
* retrieve data <------------------|
|
* If it wasn't, Desktop deletes the session. If it was, the account
|
||||||
* verify HMAC |
|
* setup can proceed. If Desktop doesn't yet have an account set up,
|
||||||
* decrypt data |
|
* it will keep the channel open and let the user connect to or
|
||||||
|
* create an account.
|
||||||
|
*
|
||||||
|
* | encrypt credentials
|
||||||
|
* |<------------- upload credentials
|
||||||
|
* retrieve credentials <-----------|
|
||||||
|
* verify HMAC |
|
||||||
|
* decrypt credentials |
|
||||||
|
* delete session ----------------->|
|
||||||
|
* start syncing |
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Create a client object like so:
|
* Create a client object like so:
|
||||||
*
|
*
|
||||||
* let client = new JPAKEClient(observer);
|
* let client = new JPAKEClient(controller);
|
||||||
*
|
*
|
||||||
* The 'observer' object must implement the following methods:
|
* The 'controller' object must implement the following methods:
|
||||||
*
|
*
|
||||||
* displayPIN(pin) -- Display the PIN to the user, only called on the client
|
* displayPIN(pin) -- Display the PIN to the user, only called on the client
|
||||||
* that didn't provide the PIN.
|
* that didn't provide the PIN.
|
||||||
*
|
*
|
||||||
|
* onPaired() -- Called when the device pairing has been established and
|
||||||
|
* we're ready to send the credentials over. To do that, the controller
|
||||||
|
* must call 'sendAndComplete()' while the channel is active.
|
||||||
|
*
|
||||||
* onComplete(data) -- Called after transfer has been completed. On
|
* onComplete(data) -- Called after transfer has been completed. On
|
||||||
* the sending side this is called with no parameter and as soon as the
|
* the sending side this is called with no parameter and as soon as the
|
||||||
* data has been uploaded, which this doesn't mean the receiving side
|
* data has been uploaded. This does not mean the receiving side has
|
||||||
* has actually retrieved them yet.
|
* actually retrieved them yet.
|
||||||
*
|
*
|
||||||
* onAbort(error) -- Called whenever an error is encountered. All errors lead
|
* onAbort(error) -- Called whenever an error is encountered. All errors lead
|
||||||
* to an abort and the process has to be started again on both sides.
|
* to an abort and the process has to be started again on both sides.
|
||||||
|
@ -113,7 +128,12 @@ const JPAKE_VERIFY_VALUE = "0123456789ABCDEF";
|
||||||
*
|
*
|
||||||
* To initiate the transfer from the sending side, call
|
* To initiate the transfer from the sending side, call
|
||||||
*
|
*
|
||||||
* client.sendWithPIN(pin, data)
|
* client.pairWithPIN(pin, true);
|
||||||
|
*
|
||||||
|
* Once the pairing has been established, the controller's 'onPaired()' method
|
||||||
|
* will be called. To then transmit the data, call
|
||||||
|
*
|
||||||
|
* client.sendAndComplete(data);
|
||||||
*
|
*
|
||||||
* To abort the process, call
|
* To abort the process, call
|
||||||
*
|
*
|
||||||
|
@ -122,8 +142,8 @@ const JPAKE_VERIFY_VALUE = "0123456789ABCDEF";
|
||||||
* Note that after completion or abort, the 'client' instance may not be reused.
|
* Note that after completion or abort, the 'client' instance may not be reused.
|
||||||
* You will have to create a new one in case you'd like to restart the process.
|
* You will have to create a new one in case you'd like to restart the process.
|
||||||
*/
|
*/
|
||||||
function JPAKEClient(observer) {
|
function JPAKEClient(controller) {
|
||||||
this.observer = observer;
|
this.controller = controller;
|
||||||
|
|
||||||
this._log = Log4Moz.repository.getLogger("Sync.JPAKEClient");
|
this._log = Log4Moz.repository.getLogger("Sync.JPAKEClient");
|
||||||
this._log.level = Log4Moz.Level[Svc.Prefs.get(
|
this._log.level = Log4Moz.Level[Svc.Prefs.get(
|
||||||
|
@ -149,6 +169,12 @@ JPAKEClient.prototype = {
|
||||||
* Public API
|
* Public API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate pairing and receive data without providing a PIN. The PIN will
|
||||||
|
* be generated and passed on to the controller to be displayed to the user.
|
||||||
|
*
|
||||||
|
* This is typically called on mobile devices where typing is tedious.
|
||||||
|
*/
|
||||||
receiveNoPIN: function receiveNoPIN() {
|
receiveNoPIN: function receiveNoPIN() {
|
||||||
this._my_signerid = JPAKE_SIGNERID_RECEIVER;
|
this._my_signerid = JPAKE_SIGNERID_RECEIVER;
|
||||||
this._their_signerid = JPAKE_SIGNERID_SENDER;
|
this._their_signerid = JPAKE_SIGNERID_SENDER;
|
||||||
|
@ -173,33 +199,87 @@ JPAKEClient.prototype = {
|
||||||
this._computeFinal,
|
this._computeFinal,
|
||||||
this._computeKeyVerification,
|
this._computeKeyVerification,
|
||||||
this._putStep,
|
this._putStep,
|
||||||
|
function(callback) {
|
||||||
|
// Allow longer time-out for the last message.
|
||||||
|
this._maxTries = Svc.Prefs.get("jpake.lastMsgMaxTries");
|
||||||
|
callback();
|
||||||
|
},
|
||||||
this._getStep,
|
this._getStep,
|
||||||
this._decryptData,
|
this._decryptData,
|
||||||
this._complete)();
|
this._complete)();
|
||||||
},
|
},
|
||||||
|
|
||||||
sendWithPIN: function sendWithPIN(pin, obj) {
|
/**
|
||||||
|
* Initiate pairing based on the PIN entered by the user.
|
||||||
|
*
|
||||||
|
* This is typically called on desktop devices where typing is easier than
|
||||||
|
* on mobile.
|
||||||
|
*
|
||||||
|
* @param pin
|
||||||
|
* 12 character string (in human-friendly base32) containing the PIN
|
||||||
|
* entered by the user.
|
||||||
|
* @param expectDelay
|
||||||
|
* Flag that indicates that a significant delay between the pairing
|
||||||
|
* and the sending should be expected. v2 and earlier of the protocol
|
||||||
|
* did not allow for this and the pairing to a v2 or earlier client
|
||||||
|
* will be aborted if this flag is 'true'.
|
||||||
|
*/
|
||||||
|
pairWithPIN: function pairWithPIN(pin, expectDelay) {
|
||||||
this._my_signerid = JPAKE_SIGNERID_SENDER;
|
this._my_signerid = JPAKE_SIGNERID_SENDER;
|
||||||
this._their_signerid = JPAKE_SIGNERID_RECEIVER;
|
this._their_signerid = JPAKE_SIGNERID_RECEIVER;
|
||||||
|
|
||||||
this._channel = pin.slice(JPAKE_LENGTH_SECRET);
|
this._channel = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
this._channelURL = this._serverURL + this._channel;
|
this._channelURL = this._serverURL + this._channel;
|
||||||
this._secret = pin.slice(0, JPAKE_LENGTH_SECRET);
|
this._secret = pin.slice(0, JPAKE_LENGTH_SECRET);
|
||||||
this._data = JSON.stringify(obj);
|
|
||||||
|
|
||||||
this._chain(this._computeStepOne,
|
this._chain(this._computeStepOne,
|
||||||
this._getStep,
|
this._getStep,
|
||||||
|
function (callback) {
|
||||||
|
// Ensure that the other client can deal with a delay for
|
||||||
|
// the last message if that's requested by the caller.
|
||||||
|
if (!expectDelay) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
if (!this._incoming.version || this._incoming.version < 3) {
|
||||||
|
return this.abort(JPAKE_ERROR_DELAYUNSUPPORTED);
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
},
|
||||||
this._putStep,
|
this._putStep,
|
||||||
this._computeStepTwo,
|
this._computeStepTwo,
|
||||||
this._getStep,
|
this._getStep,
|
||||||
this._putStep,
|
this._putStep,
|
||||||
this._computeFinal,
|
this._computeFinal,
|
||||||
this._getStep,
|
this._getStep,
|
||||||
this._encryptData,
|
this._verifyPairing)();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data after a successful pairing.
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* Object containing the data to send. It will be serialized as JSON.
|
||||||
|
*/
|
||||||
|
sendAndComplete: function sendAndComplete(obj) {
|
||||||
|
if (!this._paired || this._finished) {
|
||||||
|
this._log.error("Can't send data, no active pairing!");
|
||||||
|
throw "No active pairing!";
|
||||||
|
}
|
||||||
|
this._data = JSON.stringify(obj);
|
||||||
|
this._chain(this._encryptData,
|
||||||
this._putStep,
|
this._putStep,
|
||||||
this._complete)();
|
this._complete)();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort the current pairing. The channel on the server will be deleted
|
||||||
|
* if the abort wasn't due to a network or server error. The controller's
|
||||||
|
* 'onAbort()' method is notified in all cases.
|
||||||
|
*
|
||||||
|
* @param error [optional]
|
||||||
|
* Error constant indicating the reason for the abort. Defaults to
|
||||||
|
* user abort.
|
||||||
|
*/
|
||||||
abort: function abort(error) {
|
abort: function abort(error) {
|
||||||
this._log.debug("Aborting...");
|
this._log.debug("Aborting...");
|
||||||
this._finished = true;
|
this._finished = true;
|
||||||
|
@ -213,10 +293,9 @@ JPAKEClient.prototype = {
|
||||||
if (error == JPAKE_ERROR_CHANNEL ||
|
if (error == JPAKE_ERROR_CHANNEL ||
|
||||||
error == JPAKE_ERROR_NETWORK ||
|
error == JPAKE_ERROR_NETWORK ||
|
||||||
error == JPAKE_ERROR_NODATA) {
|
error == JPAKE_ERROR_NODATA) {
|
||||||
Utils.namedTimer(function() { this.observer.onAbort(error); }, 0,
|
Utils.nextTick(function() { this.controller.onAbort(error); }, this);
|
||||||
this, "_timer_onAbort");
|
|
||||||
} else {
|
} else {
|
||||||
this._reportFailure(error, function() { self.observer.onAbort(error); });
|
this._reportFailure(error, function() { self.controller.onAbort(error); });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -285,8 +364,7 @@ JPAKEClient.prototype = {
|
||||||
|
|
||||||
// Don't block on UI code.
|
// Don't block on UI code.
|
||||||
let pin = this._secret + this._channel;
|
let pin = this._secret + this._channel;
|
||||||
Utils.namedTimer(function() { this.observer.displayPIN(pin); }, 0,
|
Utils.nextTick(function() { this.controller.displayPIN(pin); }, this);
|
||||||
this, "_timer_displayPIN");
|
|
||||||
callback();
|
callback();
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
@ -295,6 +373,11 @@ JPAKEClient.prototype = {
|
||||||
_putStep: function _putStep(callback) {
|
_putStep: function _putStep(callback) {
|
||||||
this._log.trace("Uploading message " + this._outgoing.type);
|
this._log.trace("Uploading message " + this._outgoing.type);
|
||||||
let request = this._newRequest(this._channelURL);
|
let request = this._newRequest(this._channelURL);
|
||||||
|
if (this._their_etag) {
|
||||||
|
request.setHeader("If-Match", this._their_etag);
|
||||||
|
} else {
|
||||||
|
request.setHeader("If-None-Match", "*");
|
||||||
|
}
|
||||||
request.put(this._outgoing, Utils.bind2(this, function (error) {
|
request.put(this._outgoing, Utils.bind2(this, function (error) {
|
||||||
if (this._finished) {
|
if (this._finished) {
|
||||||
return;
|
return;
|
||||||
|
@ -313,7 +396,7 @@ JPAKEClient.prototype = {
|
||||||
}
|
}
|
||||||
// There's no point in returning early here since the next step will
|
// There's no point in returning early here since the next step will
|
||||||
// always be a GET so let's pause for twice the poll interval.
|
// always be a GET so let's pause for twice the poll interval.
|
||||||
this._etag = request.response.headers["etag"];
|
this._my_etag = request.response.headers["etag"];
|
||||||
Utils.namedTimer(function () { callback(); }, this._pollInterval * 2,
|
Utils.namedTimer(function () { callback(); }, this._pollInterval * 2,
|
||||||
this, "_pollTimer");
|
this, "_pollTimer");
|
||||||
}));
|
}));
|
||||||
|
@ -324,8 +407,8 @@ JPAKEClient.prototype = {
|
||||||
_getStep: function _getStep(callback) {
|
_getStep: function _getStep(callback) {
|
||||||
this._log.trace("Retrieving next message.");
|
this._log.trace("Retrieving next message.");
|
||||||
let request = this._newRequest(this._channelURL);
|
let request = this._newRequest(this._channelURL);
|
||||||
if (this._etag) {
|
if (this._my_etag) {
|
||||||
request.setHeader("If-None-Match", this._etag);
|
request.setHeader("If-None-Match", this._my_etag);
|
||||||
}
|
}
|
||||||
|
|
||||||
request.get(Utils.bind2(this, function (error) {
|
request.get(Utils.bind2(this, function (error) {
|
||||||
|
@ -365,6 +448,14 @@ JPAKEClient.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._their_etag = request.response.headers["etag"];
|
||||||
|
if (!this._their_etag) {
|
||||||
|
this._log.error("Server did not supply ETag for message: "
|
||||||
|
+ request.response.body);
|
||||||
|
this.abort(JPAKE_ERROR_SERVER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._incoming = JSON.parse(request.response.body);
|
this._incoming = JSON.parse(request.response.body);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -414,7 +505,9 @@ JPAKEClient.prototype = {
|
||||||
gx2: gx2.value,
|
gx2: gx2.value,
|
||||||
zkp_x1: {gr: gv1.value, b: r1.value, id: this._my_signerid},
|
zkp_x1: {gr: gv1.value, b: r1.value, id: this._my_signerid},
|
||||||
zkp_x2: {gr: gv2.value, b: r2.value, id: this._my_signerid}};
|
zkp_x2: {gr: gv2.value, b: r2.value, id: this._my_signerid}};
|
||||||
this._outgoing = {type: this._my_signerid + "1", payload: one};
|
this._outgoing = {type: this._my_signerid + "1",
|
||||||
|
version: KEYEXCHANGE_VERSION,
|
||||||
|
payload: one};
|
||||||
this._log.trace("Generated message " + this._outgoing.type);
|
this._log.trace("Generated message " + this._outgoing.type);
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
|
@ -452,7 +545,9 @@ JPAKEClient.prototype = {
|
||||||
}
|
}
|
||||||
let two = {A: A.value,
|
let two = {A: A.value,
|
||||||
zkp_A: {gr: gvA.value, b: rA.value, id: this._my_signerid}};
|
zkp_A: {gr: gvA.value, b: rA.value, id: this._my_signerid}};
|
||||||
this._outgoing = {type: this._my_signerid + "2", payload: two};
|
this._outgoing = {type: this._my_signerid + "2",
|
||||||
|
version: KEYEXCHANGE_VERSION,
|
||||||
|
payload: two};
|
||||||
this._log.trace("Generated message " + this._outgoing.type);
|
this._log.trace("Generated message " + this._outgoing.type);
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
|
@ -504,12 +599,13 @@ JPAKEClient.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._outgoing = {type: this._my_signerid + "3",
|
this._outgoing = {type: this._my_signerid + "3",
|
||||||
|
version: KEYEXCHANGE_VERSION,
|
||||||
payload: {ciphertext: ciphertext, IV: iv}};
|
payload: {ciphertext: ciphertext, IV: iv}};
|
||||||
this._log.trace("Generated message " + this._outgoing.type);
|
this._log.trace("Generated message " + this._outgoing.type);
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
|
|
||||||
_encryptData: function _encryptData(callback) {
|
_verifyPairing: function _verifyPairing(callback) {
|
||||||
this._log.trace("Verifying their key.");
|
this._log.trace("Verifying their key.");
|
||||||
if (this._incoming.type != this._their_signerid + "3") {
|
if (this._incoming.type != this._their_signerid + "3") {
|
||||||
this._log.error("Invalid round 3 data: " +
|
this._log.error("Invalid round 3 data: " +
|
||||||
|
@ -518,6 +614,7 @@ JPAKEClient.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let step3 = this._incoming.payload;
|
let step3 = this._incoming.payload;
|
||||||
|
let ciphertext;
|
||||||
try {
|
try {
|
||||||
ciphertext = Svc.Crypto.encrypt(JPAKE_VERIFY_VALUE,
|
ciphertext = Svc.Crypto.encrypt(JPAKE_VERIFY_VALUE,
|
||||||
this._crypto_key, step3.IV);
|
this._crypto_key, step3.IV);
|
||||||
|
@ -530,6 +627,13 @@ JPAKEClient.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._log.debug("Verified pairing!");
|
||||||
|
this._paired = true;
|
||||||
|
Utils.nextTick(function () { this.controller.onPaired(); }, this);
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
|
||||||
|
_encryptData: function _encryptData(callback) {
|
||||||
this._log.trace("Encrypting data.");
|
this._log.trace("Encrypting data.");
|
||||||
let iv, ciphertext, hmac;
|
let iv, ciphertext, hmac;
|
||||||
try {
|
try {
|
||||||
|
@ -542,6 +646,7 @@ JPAKEClient.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._outgoing = {type: this._my_signerid + "3",
|
this._outgoing = {type: this._my_signerid + "3",
|
||||||
|
version: KEYEXCHANGE_VERSION,
|
||||||
payload: {ciphertext: ciphertext, IV: iv, hmac: hmac}};
|
payload: {ciphertext: ciphertext, IV: iv, hmac: hmac}};
|
||||||
this._log.trace("Generated message " + this._outgoing.type);
|
this._log.trace("Generated message " + this._outgoing.type);
|
||||||
callback();
|
callback();
|
||||||
|
@ -594,8 +699,8 @@ JPAKEClient.prototype = {
|
||||||
_complete: function _complete() {
|
_complete: function _complete() {
|
||||||
this._log.debug("Exchange completed.");
|
this._log.debug("Exchange completed.");
|
||||||
this._finished = true;
|
this._finished = true;
|
||||||
Utils.namedTimer(function () { this.observer.onComplete(this._newData); },
|
Utils.nextTick(function () { this.controller.onComplete(this._newData); },
|
||||||
0, this, "_timer_onComplete");
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,8 @@ pref("services.sync.engine.tabs.filteredUrls", "^(about:.*|chrome://weave/.*|wyc
|
||||||
|
|
||||||
pref("services.sync.jpake.serverURL", "https://setup.services.mozilla.com/");
|
pref("services.sync.jpake.serverURL", "https://setup.services.mozilla.com/");
|
||||||
pref("services.sync.jpake.pollInterval", 1000);
|
pref("services.sync.jpake.pollInterval", 1000);
|
||||||
pref("services.sync.jpake.firstMsgMaxTries", 300);
|
pref("services.sync.jpake.firstMsgMaxTries", 300); // 5 minutes
|
||||||
|
pref("services.sync.jpake.lastMsgMaxTries", 300); // 5 minutes
|
||||||
pref("services.sync.jpake.maxTries", 10);
|
pref("services.sync.jpake.maxTries", 10);
|
||||||
|
|
||||||
pref("services.sync.log.appender.console", "Warn");
|
pref("services.sync.log.appender.console", "Warn");
|
||||||
|
|
|
@ -6,26 +6,32 @@ Cu.import("resource://services-sync/util.js");
|
||||||
|
|
||||||
const JPAKE_LENGTH_SECRET = 8;
|
const JPAKE_LENGTH_SECRET = 8;
|
||||||
const JPAKE_LENGTH_CLIENTID = 256;
|
const JPAKE_LENGTH_CLIENTID = 256;
|
||||||
|
const KEYEXCHANGE_VERSION = 3;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Simple server.
|
* Simple server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const SERVER_MAX_GETS = 6;
|
||||||
|
|
||||||
function check_headers(request) {
|
function check_headers(request) {
|
||||||
|
let stack = Components.stack.caller;
|
||||||
|
|
||||||
// There shouldn't be any Basic auth
|
// There shouldn't be any Basic auth
|
||||||
do_check_false(request.hasHeader("Authorization"));
|
do_check_false(request.hasHeader("Authorization"), stack);
|
||||||
|
|
||||||
// Ensure key exchange ID is set and the right length
|
// Ensure key exchange ID is set and the right length
|
||||||
do_check_true(request.hasHeader("X-KeyExchange-Id"));
|
do_check_true(request.hasHeader("X-KeyExchange-Id"), stack);
|
||||||
do_check_eq(request.getHeader("X-KeyExchange-Id").length,
|
do_check_eq(request.getHeader("X-KeyExchange-Id").length,
|
||||||
JPAKE_LENGTH_CLIENTID);
|
JPAKE_LENGTH_CLIENTID, stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
function new_channel() {
|
function new_channel() {
|
||||||
// Create a new channel and register it with the server.
|
// Create a new channel and register it with the server.
|
||||||
let cid = Math.floor(Math.random() * 10000);
|
let cid = Math.floor(Math.random() * 10000);
|
||||||
while (channels[cid])
|
while (channels[cid]) {
|
||||||
cid = Math.floor(Math.random() * 10000);
|
cid = Math.floor(Math.random() * 10000);
|
||||||
|
}
|
||||||
let channel = channels[cid] = new ServerChannel();
|
let channel = channels[cid] = new ServerChannel();
|
||||||
server.registerPathHandler("/" + cid, channel.handler());
|
server.registerPathHandler("/" + cid, channel.handler());
|
||||||
return cid;
|
return cid;
|
||||||
|
@ -45,21 +51,24 @@ let error_report;
|
||||||
function server_report(request, response) {
|
function server_report(request, response) {
|
||||||
check_headers(request);
|
check_headers(request);
|
||||||
|
|
||||||
if (request.hasHeader("X-KeyExchange-Log"))
|
if (request.hasHeader("X-KeyExchange-Log")) {
|
||||||
error_report = request.getHeader("X-KeyExchange-Log");
|
error_report = request.getHeader("X-KeyExchange-Log");
|
||||||
|
}
|
||||||
|
|
||||||
if (request.hasHeader("X-KeyExchange-Cid")) {
|
if (request.hasHeader("X-KeyExchange-Cid")) {
|
||||||
let cid = request.getHeader("X-KeyExchange-Cid");
|
let cid = request.getHeader("X-KeyExchange-Cid");
|
||||||
let channel = channels[cid];
|
let channel = channels[cid];
|
||||||
if (channel)
|
if (channel) {
|
||||||
channel.clear();
|
channel.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServerChannel() {
|
function ServerChannel() {
|
||||||
this.data = "{}";
|
this.data = "";
|
||||||
|
this.etag = "";
|
||||||
this.getCount = 0;
|
this.getCount = 0;
|
||||||
}
|
}
|
||||||
ServerChannel.prototype = {
|
ServerChannel.prototype = {
|
||||||
|
@ -69,26 +78,42 @@ ServerChannel.prototype = {
|
||||||
response.setStatusLine(request.httpVersion, 404, "Not Found");
|
response.setStatusLine(request.httpVersion, 404, "Not Found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.hasHeader("If-None-Match")) {
|
if (request.hasHeader("If-None-Match")) {
|
||||||
let etag = request.getHeader("If-None-Match");
|
let etag = request.getHeader("If-None-Match");
|
||||||
if (etag == this._etag) {
|
if (etag == this.etag) {
|
||||||
response.setStatusLine(request.httpVersion, 304, "Not Modified");
|
response.setStatusLine(request.httpVersion, 304, "Not Modified");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
response.setHeader("ETag", this.etag);
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||||
response.bodyOutputStream.write(this.data, this.data.length);
|
response.bodyOutputStream.write(this.data, this.data.length);
|
||||||
|
|
||||||
// Automatically clear the channel after 6 successful GETs.
|
// Automatically clear the channel after 6 successful GETs.
|
||||||
this.getCount += 1;
|
this.getCount += 1;
|
||||||
if (this.getCount == 6)
|
if (this.getCount == SERVER_MAX_GETS) {
|
||||||
this.clear();
|
this.clear();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
PUT: function PUT(request, response) {
|
PUT: function PUT(request, response) {
|
||||||
|
if (this.data) {
|
||||||
|
do_check_true(request.hasHeader("If-Match"));
|
||||||
|
let etag = request.getHeader("If-Match");
|
||||||
|
if (etag != this.etag) {
|
||||||
|
response.setHeader("ETag", this.etag);
|
||||||
|
response.setStatusLine(request.httpVersion, 412, "Precondition Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
do_check_true(request.hasHeader("If-None-Match"));
|
||||||
|
do_check_eq(request.getHeader("If-None-Match"), "*");
|
||||||
|
}
|
||||||
|
|
||||||
this.data = readBytesFromInputStream(request.bodyInputStream);
|
this.data = readBytesFromInputStream(request.bodyInputStream);
|
||||||
this._etag = '"' + Utils.sha1(this.data) + '"';
|
this.etag = '"' + Utils.sha1(this.data) + '"';
|
||||||
response.setHeader("ETag", this._etag);
|
response.setHeader("ETag", this.etag);
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -108,14 +133,34 @@ ServerChannel.prototype = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller that throws for everything.
|
||||||
|
*/
|
||||||
|
let BaseController = {
|
||||||
|
displayPIN: function displayPIN() {
|
||||||
|
do_throw("displayPIN() shouldn't have been called!");
|
||||||
|
},
|
||||||
|
onAbort: function onAbort(error) {
|
||||||
|
do_throw("Shouldn't have aborted with " + error + "!");
|
||||||
|
},
|
||||||
|
onPaired: function onPaired() {
|
||||||
|
do_throw("onPaired() shouldn't have been called!");
|
||||||
|
},
|
||||||
|
onComplete: function onComplete(data) {
|
||||||
|
do_throw("Shouldn't have completed with " + data + "!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const DATA = {"msg": "eggstreamly sekrit"};
|
const DATA = {"msg": "eggstreamly sekrit"};
|
||||||
const POLLINTERVAL = 50;
|
const POLLINTERVAL = 50;
|
||||||
|
|
||||||
function run_test() {
|
function run_test() {
|
||||||
Svc.Prefs.set("jpake.serverURL", "http://localhost:8080/");
|
Svc.Prefs.set("jpake.serverURL", "http://localhost:8080/");
|
||||||
Svc.Prefs.set("jpake.pollInterval", POLLINTERVAL);
|
Svc.Prefs.set("jpake.pollInterval", POLLINTERVAL);
|
||||||
Svc.Prefs.set("jpake.maxTries", 5);
|
Svc.Prefs.set("jpake.maxTries", 2);
|
||||||
Svc.Prefs.set("jpake.firstMsgMaxTries", 5);
|
Svc.Prefs.set("jpake.firstMsgMaxTries", 5);
|
||||||
|
Svc.Prefs.set("jpake.lastMsgMaxTries", 5);
|
||||||
// Ensure clean up
|
// Ensure clean up
|
||||||
Svc.Obs.add("profile-before-change", function() {
|
Svc.Obs.add("profile-before-change", function() {
|
||||||
Svc.Prefs.resetBranch("");
|
Svc.Prefs.resetBranch("");
|
||||||
|
@ -134,6 +179,8 @@ function run_test() {
|
||||||
"/report": server_report});
|
"/report": server_report});
|
||||||
|
|
||||||
initTestLogging("Trace");
|
initTestLogging("Trace");
|
||||||
|
Log4Moz.repository.getLogger("Sync.JPAKEClient").level = Log4Moz.Level.Trace;
|
||||||
|
Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace;
|
||||||
run_next_test();
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,23 +189,20 @@ add_test(function test_success_receiveNoPIN() {
|
||||||
_("Test a successful exchange started by receiveNoPIN().");
|
_("Test a successful exchange started by receiveNoPIN().");
|
||||||
|
|
||||||
let snd = new JPAKEClient({
|
let snd = new JPAKEClient({
|
||||||
displayPIN: function displayPIN() {
|
__proto__: BaseController,
|
||||||
do_throw("displayPIN shouldn't have been called!");
|
onPaired: function onPaired() {
|
||||||
},
|
_("Pairing successful, sending final payload.");
|
||||||
onAbort: function onAbort(error) {
|
Utils.nextTick(function() { snd.sendAndComplete(DATA); });
|
||||||
do_throw("Shouldn't have aborted!" + error);
|
|
||||||
},
|
},
|
||||||
onComplete: function onComplete() {}
|
onComplete: function onComplete() {}
|
||||||
});
|
});
|
||||||
|
|
||||||
let rec = new JPAKEClient({
|
let rec = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
displayPIN: function displayPIN(pin) {
|
displayPIN: function displayPIN(pin) {
|
||||||
_("Received PIN " + pin + ". Entering it in the other computer...");
|
_("Received PIN " + pin + ". Entering it in the other computer...");
|
||||||
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
Utils.nextTick(function() { snd.sendWithPIN(pin, DATA); });
|
Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
|
||||||
do_throw("Shouldn't have aborted! " + error);
|
|
||||||
},
|
},
|
||||||
onComplete: function onComplete(a) {
|
onComplete: function onComplete(a) {
|
||||||
// Ensure channel was cleared, no error report.
|
// Ensure channel was cleared, no error report.
|
||||||
|
@ -171,10 +215,11 @@ add_test(function test_success_receiveNoPIN() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
add_test(function test_firstMsgMaxTries() {
|
add_test(function test_firstMsgMaxTries_timeout() {
|
||||||
_("Test abort when sender doesn't upload anything.");
|
_("Test abort when sender doesn't upload anything.");
|
||||||
|
|
||||||
let rec = new JPAKEClient({
|
let rec = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
displayPIN: function displayPIN(pin) {
|
displayPIN: function displayPIN(pin) {
|
||||||
_("Received PIN " + pin + ". Doing nothing...");
|
_("Received PIN " + pin + ". Doing nothing...");
|
||||||
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
|
@ -186,33 +231,98 @@ add_test(function test_firstMsgMaxTries() {
|
||||||
do_check_eq(error_report, JPAKE_ERROR_TIMEOUT);
|
do_check_eq(error_report, JPAKE_ERROR_TIMEOUT);
|
||||||
error_report = undefined;
|
error_report = undefined;
|
||||||
run_next_test();
|
run_next_test();
|
||||||
},
|
|
||||||
onComplete: function onComplete() {
|
|
||||||
do_throw("Shouldn't have completed! ");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
rec.receiveNoPIN();
|
rec.receiveNoPIN();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
add_test(function test_firstMsgMaxTries() {
|
||||||
|
_("Test that receiver can wait longer for the first message.");
|
||||||
|
|
||||||
|
let snd = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
|
onPaired: function onPaired() {
|
||||||
|
_("Pairing successful, sending final payload.");
|
||||||
|
Utils.nextTick(function() { snd.sendAndComplete(DATA); });
|
||||||
|
},
|
||||||
|
onComplete: function onComplete() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
let rec = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
|
displayPIN: function displayPIN(pin) {
|
||||||
|
// For the purpose of the tests, the poll interval is 50ms and
|
||||||
|
// we're polling up to 5 times for the first exchange (as
|
||||||
|
// opposed to 2 times for most of the other exchanges). So let's
|
||||||
|
// pretend it took 150ms to enter the PIN on the sender.
|
||||||
|
_("Received PIN " + pin + ". Waiting 150ms before entering it into sender...");
|
||||||
|
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
|
Utils.namedTimer(function() { snd.pairWithPIN(pin, false); },
|
||||||
|
150, this, "_sendTimer");
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rec.receiveNoPIN();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
add_test(function test_lastMsgMaxTries() {
|
||||||
|
_("Test that receiver can wait longer for the last message.");
|
||||||
|
|
||||||
|
let snd = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
|
onPaired: function onPaired() {
|
||||||
|
// For the purpose of the tests, the poll interval is 50ms and
|
||||||
|
// we're polling up to 5 times for the last exchange (as opposed
|
||||||
|
// to 2 times for other exchanges). So let's pretend it took
|
||||||
|
// 150ms to come up with the final payload, which should require
|
||||||
|
// 3 polls.
|
||||||
|
_("Pairing successful, waiting 150ms to send final payload.");
|
||||||
|
Utils.namedTimer(function() { snd.sendAndComplete(DATA); },
|
||||||
|
150, this, "_sendTimer");
|
||||||
|
},
|
||||||
|
onComplete: function onComplete() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
let rec = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
|
displayPIN: function displayPIN(pin) {
|
||||||
|
_("Received PIN " + pin + ". Entering it in the other computer...");
|
||||||
|
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
|
Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rec.receiveNoPIN();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
add_test(function test_wrongPIN() {
|
add_test(function test_wrongPIN() {
|
||||||
_("Test abort when PINs don't match.");
|
_("Test abort when PINs don't match.");
|
||||||
|
|
||||||
let snd = new JPAKEClient({
|
let snd = new JPAKEClient({
|
||||||
displayPIN: function displayPIN() {
|
__proto__: BaseController,
|
||||||
do_throw("displayPIN shouldn't have been called!");
|
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
do_check_eq(error, JPAKE_ERROR_KEYMISMATCH);
|
do_check_eq(error, JPAKE_ERROR_KEYMISMATCH);
|
||||||
do_check_eq(error_report, JPAKE_ERROR_KEYMISMATCH);
|
do_check_eq(error_report, JPAKE_ERROR_KEYMISMATCH);
|
||||||
error_report = undefined;
|
error_report = undefined;
|
||||||
},
|
|
||||||
onComplete: function onComplete() {
|
|
||||||
do_throw("Shouldn't have completed!");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let rec = new JPAKEClient({
|
let rec = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
displayPIN: function displayPIN(pin) {
|
displayPIN: function displayPIN(pin) {
|
||||||
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
let secret = pin.slice(0, JPAKE_LENGTH_SECRET);
|
let secret = pin.slice(0, JPAKE_LENGTH_SECRET);
|
||||||
|
@ -220,16 +330,13 @@ add_test(function test_wrongPIN() {
|
||||||
let new_pin = secret + this.cid;
|
let new_pin = secret + this.cid;
|
||||||
_("Received PIN " + pin + ", but I'm entering " + new_pin);
|
_("Received PIN " + pin + ", but I'm entering " + new_pin);
|
||||||
|
|
||||||
Utils.nextTick(function() { snd.sendWithPIN(new_pin, DATA); });
|
Utils.nextTick(function() { snd.pairWithPIN(new_pin, false); });
|
||||||
},
|
},
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
do_check_eq(error, JPAKE_ERROR_NODATA);
|
do_check_eq(error, JPAKE_ERROR_NODATA);
|
||||||
// Ensure channel was cleared.
|
// Ensure channel was cleared.
|
||||||
do_check_eq(channels[this.cid].data, undefined);
|
do_check_eq(channels[this.cid].data, undefined);
|
||||||
run_next_test();
|
run_next_test();
|
||||||
},
|
|
||||||
onComplete: function onComplete() {
|
|
||||||
do_throw("Shouldn't have completed! ");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
rec.receiveNoPIN();
|
rec.receiveNoPIN();
|
||||||
|
@ -240,9 +347,7 @@ add_test(function test_abort_receiver() {
|
||||||
_("Test user abort on receiving side.");
|
_("Test user abort on receiving side.");
|
||||||
|
|
||||||
let rec = new JPAKEClient({
|
let rec = new JPAKEClient({
|
||||||
onComplete: function onComplete(data) {
|
__proto__: BaseController,
|
||||||
do_throw("onComplete shouldn't be called.");
|
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
// Manual abort = userabort.
|
// Manual abort = userabort.
|
||||||
do_check_eq(error, JPAKE_ERROR_USERABORT);
|
do_check_eq(error, JPAKE_ERROR_USERABORT);
|
||||||
|
@ -265,24 +370,17 @@ add_test(function test_abort_sender() {
|
||||||
_("Test user abort on sending side.");
|
_("Test user abort on sending side.");
|
||||||
|
|
||||||
let snd = new JPAKEClient({
|
let snd = new JPAKEClient({
|
||||||
displayPIN: function displayPIN() {
|
__proto__: BaseController,
|
||||||
do_throw("displayPIN shouldn't have been called!");
|
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
// Manual abort == userabort.
|
// Manual abort == userabort.
|
||||||
do_check_eq(error, JPAKE_ERROR_USERABORT);
|
do_check_eq(error, JPAKE_ERROR_USERABORT);
|
||||||
do_check_eq(error_report, JPAKE_ERROR_USERABORT);
|
do_check_eq(error_report, JPAKE_ERROR_USERABORT);
|
||||||
error_report = undefined;
|
error_report = undefined;
|
||||||
},
|
|
||||||
onComplete: function onComplete() {
|
|
||||||
do_throw("Shouldn't have completed!");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let rec = new JPAKEClient({
|
let rec = new JPAKEClient({
|
||||||
onComplete: function onComplete(data) {
|
__proto__: BaseController,
|
||||||
do_throw("onComplete shouldn't be called.");
|
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
do_check_eq(error, JPAKE_ERROR_NODATA);
|
do_check_eq(error, JPAKE_ERROR_NODATA);
|
||||||
// Ensure channel was cleared, no error report.
|
// Ensure channel was cleared, no error report.
|
||||||
|
@ -293,7 +391,7 @@ add_test(function test_abort_sender() {
|
||||||
displayPIN: function displayPIN(pin) {
|
displayPIN: function displayPIN(pin) {
|
||||||
_("Received PIN " + pin + ". Entering it in the other computer...");
|
_("Received PIN " + pin + ". Entering it in the other computer...");
|
||||||
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
|
||||||
Utils.nextTick(function() { snd.sendWithPIN(pin, DATA); });
|
Utils.nextTick(function() { snd.pairWithPIN(pin, false); });
|
||||||
Utils.namedTimer(function() { snd.abort(); },
|
Utils.namedTimer(function() { snd.abort(); },
|
||||||
POLLINTERVAL, this, "_abortTimer");
|
POLLINTERVAL, this, "_abortTimer");
|
||||||
}
|
}
|
||||||
|
@ -304,8 +402,13 @@ add_test(function test_abort_sender() {
|
||||||
|
|
||||||
add_test(function test_wrongmessage() {
|
add_test(function test_wrongmessage() {
|
||||||
let cid = new_channel();
|
let cid = new_channel();
|
||||||
channels[cid].data = JSON.stringify({type: "receiver2", payload: {}});
|
let channel = channels[cid];
|
||||||
|
channel.data = JSON.stringify({type: "receiver2",
|
||||||
|
version: KEYEXCHANGE_VERSION,
|
||||||
|
payload: {}});
|
||||||
|
channel.etag = '"fake-etag"';
|
||||||
let snd = new JPAKEClient({
|
let snd = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
onComplete: function onComplete(data) {
|
onComplete: function onComplete(data) {
|
||||||
do_throw("onComplete shouldn't be called.");
|
do_throw("onComplete shouldn't be called.");
|
||||||
},
|
},
|
||||||
|
@ -314,20 +417,19 @@ add_test(function test_wrongmessage() {
|
||||||
run_next_test();
|
run_next_test();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
snd.sendWithPIN("01234567" + cid, DATA);
|
snd.pairWithPIN("01234567" + cid, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
add_test(function test_error_channel() {
|
add_test(function test_error_channel() {
|
||||||
|
let serverURL = Svc.Prefs.get("jpake.serverURL");
|
||||||
Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
|
Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
|
||||||
|
|
||||||
let rec = new JPAKEClient({
|
let rec = new JPAKEClient({
|
||||||
onComplete: function onComplete(data) {
|
__proto__: BaseController,
|
||||||
do_throw("onComplete shouldn't be called.");
|
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
do_check_eq(error, JPAKE_ERROR_CHANNEL);
|
do_check_eq(error, JPAKE_ERROR_CHANNEL);
|
||||||
Svc.Prefs.reset("jpake.serverURL");
|
Svc.Prefs.set("jpake.serverURL", serverURL);
|
||||||
run_next_test();
|
run_next_test();
|
||||||
},
|
},
|
||||||
displayPIN: function displayPIN(pin) {}
|
displayPIN: function displayPIN(pin) {}
|
||||||
|
@ -337,19 +439,64 @@ add_test(function test_error_channel() {
|
||||||
|
|
||||||
|
|
||||||
add_test(function test_error_network() {
|
add_test(function test_error_network() {
|
||||||
|
let serverURL = Svc.Prefs.get("jpake.serverURL");
|
||||||
Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
|
Svc.Prefs.set("jpake.serverURL", "http://localhost:12345/");
|
||||||
|
|
||||||
let snd = new JPAKEClient({
|
let snd = new JPAKEClient({
|
||||||
onComplete: function onComplete(data) {
|
__proto__: BaseController,
|
||||||
do_throw("onComplete shouldn't be called.");
|
|
||||||
},
|
|
||||||
onAbort: function onAbort(error) {
|
onAbort: function onAbort(error) {
|
||||||
do_check_eq(error, JPAKE_ERROR_NETWORK);
|
do_check_eq(error, JPAKE_ERROR_NETWORK);
|
||||||
Svc.Prefs.reset("jpake.serverURL");
|
Svc.Prefs.set("jpake.serverURL", serverURL);
|
||||||
run_next_test();
|
run_next_test();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
snd.sendWithPIN("0123456789ab", DATA);
|
snd.pairWithPIN("0123456789ab", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
add_test(function test_error_server_noETag() {
|
||||||
|
let cid = new_channel();
|
||||||
|
let channel = channels[cid];
|
||||||
|
channel.data = JSON.stringify({type: "receiver1",
|
||||||
|
version: KEYEXCHANGE_VERSION,
|
||||||
|
payload: {}});
|
||||||
|
// This naughty server doesn't supply ETag (well, it supplies empty one).
|
||||||
|
channel.etag = "";
|
||||||
|
let snd = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
|
onAbort: function onAbort(error) {
|
||||||
|
do_check_eq(error, JPAKE_ERROR_SERVER);
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
snd.pairWithPIN("01234567" + cid, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
add_test(function test_error_delayNotSupported() {
|
||||||
|
let cid = new_channel();
|
||||||
|
let channel = channels[cid];
|
||||||
|
channel.data = JSON.stringify({type: "receiver1",
|
||||||
|
version: 2,
|
||||||
|
payload: {}});
|
||||||
|
channel.etag = '"fake-etag"';
|
||||||
|
let snd = new JPAKEClient({
|
||||||
|
__proto__: BaseController,
|
||||||
|
onAbort: function onAbort(error) {
|
||||||
|
do_check_eq(error, JPAKE_ERROR_DELAYUNSUPPORTED);
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
snd.pairWithPIN("01234567" + cid, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
add_test(function test_sendAndComplete_notPaired() {
|
||||||
|
let snd = new JPAKEClient({__proto__: BaseController});
|
||||||
|
do_check_throws(function () {
|
||||||
|
snd.sendAndComplete(DATA);
|
||||||
|
});
|
||||||
|
run_next_test();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче