Retry API, standardize <script> list

This commit is contained in:
Matt Corallo 2014-06-01 13:39:35 -04:00
parent 8f49d201e6
commit cf35b7056f
7 changed files with 143 additions and 70 deletions

View file

@ -14,8 +14,6 @@
<html>
<head>
<script type="text/javascript" src="js/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js-deps/nacl-common.js"></script>
<script type="text/javascript" src="js-deps/jquery.js"></script>
<script type="text/javascript" src="js-deps/core.js"></script>
@ -33,9 +31,12 @@
<script type="text/javascript" src="js-deps/underscore.js"></script>
<script type="text/javascript" src="js-deps/backbone.js"></script>
<script type="text/javascript" src="js-deps/backbone.localStorage.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js/models/messages.js"></script>
<script type="text/javascript" src="js/models/threads.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/background.js"></script>

View file

@ -104,6 +104,8 @@ window.textsecure.api = function() {
textsecure.throwHumanError(code, "HTTPError", "The server rejected our query, please file a bug report.");
}
} catch (e) {
if (jqXHR.responseJSON)
e.response = jqXHR.responseJSON;
reject(e);
}
}

View file

@ -238,12 +238,6 @@ window.textsecure.crypto = function() {
throw new Error("Datastore inconsistency: device was stored without identity key");
}
// Used when device keys change - we assume key compromise so refuse all new messages
self.forceRemoveAllSessions = function(encodedNumber) {
textsecure.storage.removeEncrypted("session" + encodedNumber);
}
/*****************************
*** Internal Crypto stuff ***
*****************************/
@ -407,7 +401,26 @@ window.textsecure.crypto = function() {
session.indexInfo.closed = new Date().getTime();
}
var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) {
var initSessionFromPreKeyWhisperMessage;
var decryptWhisperMessage;
var handlePreKeyWhisperMessage = function(from, encodedMessage) {
var preKeyProto = textsecure.protos.decodePreKeyWhisperMessageProtobuf(encodedMessage);
return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) {
return decryptWhisperMessage(from, getString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) {
if (sessions[1] !== undefined)
crypto_storage.saveSession(from, sessions[1]);
return result;
});
});
}
var wipeIdentityAndTryMessageAgain = function(from, encodedMessage) {
//TODO: Wipe identity key!
return handlePreKeyWhisperMessage(from, encodedMessage);
}
textsecure.replay.registerReplayFunction(wipeIdentityAndTryMessageAgain, textsecure.replay.REPLAY_FUNCS.INIT_SESSION);
initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) {
var preKeyPair = crypto_storage.getAndRemovePreKeyPair(message.preKeyId);
var session = crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, toArrayBuffer(message.baseKey));
@ -428,8 +441,7 @@ window.textsecure.crypto = function() {
closeSession(open_session); // To be returned and saved later
} else {
// ...otherwise create an error that the UI will pick up and ask the user if they want to re-negotiate
// TODO: Save the message for possible later renegotiation
textsecure.throwHumanError("Received message with unknown identity key", "WarnTryAgainError", "The identity of the sender has changed. This may be malicious, or the sender may have simply reinstalled TextSecure.");
throw textsecure.createTryAgainError("Received message with unknown identity key", "The identity of the sender has changed. This may be malicious, or the sender may have simply reinstalled TextSecure.", textsecure.replay.REPLAY_FUNCS.INIT_SESSION, [encodedNumber, getString(message.encode())]);
}
}
return initSession(false, preKeyPair, encodedNumber, toArrayBuffer(message.identityKey), toArrayBuffer(message.baseKey))
@ -525,7 +537,7 @@ window.textsecure.crypto = function() {
}
// returns decrypted protobuf
var decryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) {
decryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) {
if (messageBytes[0] != String.fromCharCode((2 << 4) | 2))
throw new Error("Bad version number on WhisperMessage");
@ -618,14 +630,7 @@ window.textsecure.crypto = function() {
if (proto.message.readUint8() != (2 << 4 | 2))
throw new Error("Bad version byte");
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
var preKeyProto = textsecure.protos.decodePreKeyWhisperMessageProtobuf(getString(proto.message));
return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) {
return decryptWhisperMessage(from, getString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) {
if (sessions[1] !== undefined)
crypto_storage.saveSession(proto.source, sessions[1]);
return result;
});
});
return handlePreKeyWhisperMessage(from, getString(proto.message));
}
}

View file

@ -389,8 +389,8 @@ window.textsecure.storage = function() {
var devicesRemoved = 0;
for (i in map.devices) {
var keep = true;
for (idToRemove in deviceIdsToRemove)
if (map.devices[i].encodedNumber == number + "." + idToRemove)
for (j in deviceIdsToRemove)
if (map.devices[i].encodedNumber == number + "." + deviceIdsToRemove[j])
keep = false;
if (keep)
@ -459,6 +459,41 @@ window.textsecure.nacl = function() {
//TODO: Some kind of textsecure.init(use_nacl)
window.textsecure.registerOnLoadFunction = window.textsecure.nacl.registerOnLoadFunction;
window.textsecure.replay = function() {
var self = {};
self.REPLAY_FUNCS = {
SEND_MESSAGE: 1,
INIT_SESSION: 2,
}
var functions = {};
self.registerReplayFunction = function(func, functionCode) {
functions[functionCode] = func;
}
self.replayError = function(replayData) {
var args = Array.prototype.slice.call(arguments);
args.shift();
args = replayData.args.concat(args);
functions[replayData.replayFunction].apply(window, args);
}
self.createReplayableError = function(shortMsg, longMsg, replayFunction, args) {
var e = new Error(shortMsg);
e.name = "ReplayableError";
e.humanError = e.longMessage = longMsg;
e.replayData = { replayFunction: replayFunction, args: args };
e.replay = function() {
self.replayError(e.replayData);
}
return e;
}
return self;
}();
// message_callback({message: decryptedMessage, pushMessage: server-providedPushMessage})
window.textsecure.subscribeToPush = function() {
var subscribeToPushMessageSemaphore = 0;
@ -539,13 +574,13 @@ window.textsecure.sendMessage = function() {
var identityKey = getString(response[0].identityKey);
for (i in response)
if (getString(response[i].identityKey) != identityKey)
throw new Error("Identity key changed");
throw new Error("Identity key not consistent");
for (i in response) {
var updateDevice = (updateDevices === undefined);
if (!updateDevice)
for (deviceId in updateDevices)
if (deviceId == response[i].deviceId)
for (j in updateDevices)
if (updateDevices[j] == response[i].deviceId)
updateDevice = true;
if (updateDevice)
@ -602,6 +637,13 @@ window.textsecure.sendMessage = function() {
});
}
var tryMessageAgain = function(number, encodedMessage, callback) {
//TODO: Wipe identity key!
var message = textsecure.protos.decodePushMessageContentProtobuf(encodedMessage);
textsecure.sendMessage([number], message, callback);
}
textsecure.replay.registerReplayFunction(tryMessageAgain, textsecure.replay.SEND_MESSAGE);
return function(numbers, message, callback) {
var numbersCompleted = 0;
var errors = [];
@ -620,28 +662,39 @@ window.textsecure.sendMessage = function() {
numberCompleted();
}
var doSendMessage = function(number, devicesForNumber, message) {
var doSendMessage;
var reloadDevicesAndSend = function(number, recurse) {
return function() {
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
if (devicesForNumber.length == 0)
registerError(number, "Go empty device list when loading device keys", null);
else
doSendMessage(number, devicesForNumber, recurse);
}
}
doSendMessage = function(number, devicesForNumber, recurse) {
return sendMessageToDevices(number, devicesForNumber, message).then(function(result) {
successfulNumbers[successfulNumbers.length] = number;
numberCompleted();
}).catch(function(error) {
if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) {
var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices);
getKeysForNumber(number, resetDevices).then(function() {
if (!recurse)
return registerError(number, "Hit retry limit attempting to reload device list", error);
if (error.message == 409)
resetDevices = resetDevices.concat(error.response.extraDevices);
textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices);
textsecure.storage.devices.removeDeviceIdsForNumber(number, resetDevices);
for (i in resetDevices)
textsecure.crypto.forceRemoveAllSessions(number + "." + resetDevices[i]);
//TODO: Try again
}).catch(function(error) {
var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices);
getKeysForNumber(number, resetDevices)
.then(reloadDevicesAndSend(number, false))
.catch(function(error) {
if (error.message !== "Identity key changed")
registerError(number, "Failed to reload device keys", error);
else {
// TODO: Identity key changed, check which devices it changed for and get upset
registerError(number, "Identity key changed!!!!", error);
error = textsecure.replay.createReplayableError("The destination's identity key has changed", "The identity of the destination has changed. This may be malicious, or the destination may have simply reinstalled TextSecure.",
textsecure.replay.SEND_MESSAGE, [number, getString(message.encode())]);
registerError(number, "Identity key changed", error);
}
});
} else
@ -654,17 +707,13 @@ window.textsecure.sendMessage = function() {
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
if (devicesForNumber.length == 0) {
getKeysForNumber(number).then(function() {
devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
if (devicesForNumber.length == 0)
registerError(number, "Failed to retreive new device keys for number " + number, null);
else
doSendMessage(number, devicesForNumber, message);
}).catch(function(error) {
getKeysForNumber(number)
.then(reloadDevicesAndSend(number, true))
.catch(function(error) {
registerError(number, "Failed to retreive new device keys for number " + number, error);
});
} else
doSendMessage(number, devicesForNumber, message);
doSendMessage(number, devicesForNumber, true);
}
}
}();

View file

@ -38,7 +38,6 @@
<div id="setup-complete" style="display: none;">
<h2>You are now registered on TextSecure with number <span id="complete-number"></span></h2>
</div>
<script type="text/javascript" src="js-deps/nacl-common.js"></script>
<script type="text/javascript" src="js-deps/jquery.js"></script>
<script type="text/javascript" src="js-deps/core.js"></script>
@ -53,9 +52,16 @@
<script type="text/javascript" src="js-deps/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js-deps/underscore.js"></script>
<script type="text/javascript" src="js-deps/backbone.js"></script>
<script type="text/javascript" src="js-deps/backbone.localStorage.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js/models/messages.js"></script>
<script type="text/javascript" src="js/models/threads.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/options.js"></script>
</body>

View file

@ -44,19 +44,8 @@
</form>
</div>
</div>
<script type="text/javascript" src="js/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js-deps/nacl-common.js"></script>
<script type="text/javascript" src="js-deps/jquery.js"></script>
<script type="text/javascript" src="js-deps/underscore.js"></script>
<script type="text/javascript" src="js-deps/backbone.js"></script>
<script type="text/javascript" src="js-deps/backbone.localStorage.js"></script>
<script type="text/javascript" src="js/models/messages.js"></script>
<script type="text/javascript" src="js/models/threads.js"></script>
<script type="text/javascript" src="js/views/notifications.js"></script>
<script type="text/javascript" src="js/views/message.js"></script>
<script type="text/javascript" src="js/views/conversation.js"></script>
<script type="text/javascript" src="js/views/messages.js"></script>
<script type="text/javascript" src="js-deps/core.js"></script>
<script type="text/javascript" src="js-deps/enc-base64.js"></script>
<script type="text/javascript" src="js-deps/cipher-core.js"></script>
@ -69,8 +58,22 @@
<script type="text/javascript" src="js-deps/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js-deps/underscore.js"></script>
<script type="text/javascript" src="js-deps/backbone.js"></script>
<script type="text/javascript" src="js-deps/backbone.localStorage.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js/models/messages.js"></script>
<script type="text/javascript" src="js/models/threads.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/views/notifications.js"></script>
<script type="text/javascript" src="js/views/message.js"></script>
<script type="text/javascript" src="js/views/conversation.js"></script>
<script type="text/javascript" src="js/views/messages.js"></script>
<script type="text/javascript" src="js/popup.js"></script>
</body>
</html>

View file

@ -29,20 +29,27 @@
<script type="text/javascript" src="js-deps/core.js"></script>
<script type="text/javascript" src="js-deps/enc-base64.js"></script>
<script type="text/javascript" src="js-deps/cipher-core.js"></script>
<script type="text/javascript" src="js-deps/lib-typedarrays.js"></script>
<script type="text/javascript" src="js-deps/aes.js"></script>
<script type="text/javascript" src="js-deps/mode-ctr-min.js"></script>
<script type="text/javascript" src="js-deps/pad-nopadding.js"></script>
<script type="text/javascript" src="js-deps/hmac-sha256.js"></script>
<script type="text/javascript" src="js-deps/curve255.js"></script>
<script type="text/javascript" src="js-deps/lib-typedarrays.js"></script>
<script type="text/javascript" src="js-deps/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.min.js"></script>
<script type="text/javascript" src="js-deps/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js-deps/underscore.js"></script>
<script type="text/javascript" src="js-deps/backbone.js"></script>
<script type="text/javascript" src="js-deps/backbone.localStorage.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="js/models/messages.js"></script>
<script type="text/javascript" src="js/models/threads.js"></script>
<script type="text/javascript" src="js/api.js"></script>
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/fake_api.js"></script>
<script type="text/javascript" src="js/test.js"></script>
</body>