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> <html>
<head> <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/nacl-common.js"></script>
<script type="text/javascript" src="js-deps/jquery.js"></script> <script type="text/javascript" src="js-deps/jquery.js"></script>
<script type="text/javascript" src="js-deps/core.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/underscore.js"></script>
<script type="text/javascript" src="js-deps/backbone.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-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/messages.js"></script>
<script type="text/javascript" src="js/models/threads.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/api.js"></script>
<script type="text/javascript" src="js/chromium.js"></script> <script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/background.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."); textsecure.throwHumanError(code, "HTTPError", "The server rejected our query, please file a bug report.");
} }
} catch (e) { } catch (e) {
if (jqXHR.responseJSON)
e.response = jqXHR.responseJSON;
reject(e); reject(e);
} }
} }

View file

@ -238,12 +238,6 @@ window.textsecure.crypto = function() {
throw new Error("Datastore inconsistency: device was stored without identity key"); 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 *** *** Internal Crypto stuff ***
*****************************/ *****************************/
@ -407,7 +401,26 @@ window.textsecure.crypto = function() {
session.indexInfo.closed = new Date().getTime(); 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 preKeyPair = crypto_storage.getAndRemovePreKeyPair(message.preKeyId);
var session = crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, toArrayBuffer(message.baseKey)); 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 closeSession(open_session); // To be returned and saved later
} else { } else {
// ...otherwise create an error that the UI will pick up and ask the user if they want to re-negotiate // ...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 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())]);
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.");
} }
} }
return initSession(false, preKeyPair, encodedNumber, toArrayBuffer(message.identityKey), toArrayBuffer(message.baseKey)) return initSession(false, preKeyPair, encodedNumber, toArrayBuffer(message.identityKey), toArrayBuffer(message.baseKey))
@ -525,7 +537,7 @@ window.textsecure.crypto = function() {
} }
// returns decrypted protobuf // returns decrypted protobuf
var decryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) { decryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) {
if (messageBytes[0] != String.fromCharCode((2 << 4) | 2)) if (messageBytes[0] != String.fromCharCode((2 << 4) | 2))
throw new Error("Bad version number on WhisperMessage"); throw new Error("Bad version number on WhisperMessage");
@ -618,14 +630,7 @@ window.textsecure.crypto = function() {
if (proto.message.readUint8() != (2 << 4 | 2)) if (proto.message.readUint8() != (2 << 4 | 2))
throw new Error("Bad version byte"); throw new Error("Bad version byte");
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
var preKeyProto = textsecure.protos.decodePreKeyWhisperMessageProtobuf(getString(proto.message)); return handlePreKeyWhisperMessage(from, 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;
});
});
} }
} }

View file

@ -389,8 +389,8 @@ window.textsecure.storage = function() {
var devicesRemoved = 0; var devicesRemoved = 0;
for (i in map.devices) { for (i in map.devices) {
var keep = true; var keep = true;
for (idToRemove in deviceIdsToRemove) for (j in deviceIdsToRemove)
if (map.devices[i].encodedNumber == number + "." + idToRemove) if (map.devices[i].encodedNumber == number + "." + deviceIdsToRemove[j])
keep = false; keep = false;
if (keep) if (keep)
@ -459,6 +459,41 @@ window.textsecure.nacl = function() {
//TODO: Some kind of textsecure.init(use_nacl) //TODO: Some kind of textsecure.init(use_nacl)
window.textsecure.registerOnLoadFunction = window.textsecure.nacl.registerOnLoadFunction; 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}) // message_callback({message: decryptedMessage, pushMessage: server-providedPushMessage})
window.textsecure.subscribeToPush = function() { window.textsecure.subscribeToPush = function() {
var subscribeToPushMessageSemaphore = 0; var subscribeToPushMessageSemaphore = 0;
@ -539,13 +574,13 @@ window.textsecure.sendMessage = function() {
var identityKey = getString(response[0].identityKey); var identityKey = getString(response[0].identityKey);
for (i in response) for (i in response)
if (getString(response[i].identityKey) != identityKey) if (getString(response[i].identityKey) != identityKey)
throw new Error("Identity key changed"); throw new Error("Identity key not consistent");
for (i in response) { for (i in response) {
var updateDevice = (updateDevices === undefined); var updateDevice = (updateDevices === undefined);
if (!updateDevice) if (!updateDevice)
for (deviceId in updateDevices) for (j in updateDevices)
if (deviceId == response[i].deviceId) if (updateDevices[j] == response[i].deviceId)
updateDevice = true; updateDevice = true;
if (updateDevice) 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) { return function(numbers, message, callback) {
var numbersCompleted = 0; var numbersCompleted = 0;
var errors = []; var errors = [];
@ -620,28 +662,39 @@ window.textsecure.sendMessage = function() {
numberCompleted(); 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) { return sendMessageToDevices(number, devicesForNumber, message).then(function(result) {
successfulNumbers[successfulNumbers.length] = number; successfulNumbers[successfulNumbers.length] = number;
numberCompleted(); numberCompleted();
}).catch(function(error) { }).catch(function(error) {
if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) { if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) {
var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices); if (!recurse)
getKeysForNumber(number, resetDevices).then(function() { return registerError(number, "Hit retry limit attempting to reload device list", error);
if (error.message == 409) if (error.message == 409)
resetDevices = resetDevices.concat(error.response.extraDevices); textsecure.storage.devices.removeDeviceIdsForNumber(number, error.response.extraDevices);
textsecure.storage.devices.removeDeviceIdsForNumber(number, resetDevices); var resetDevices = ((error.message == 410) ? error.response.staleDevices : error.response.missingDevices);
for (i in resetDevices) getKeysForNumber(number, resetDevices)
textsecure.crypto.forceRemoveAllSessions(number + "." + resetDevices[i]); .then(reloadDevicesAndSend(number, false))
.catch(function(error) {
//TODO: Try again
}).catch(function(error) {
if (error.message !== "Identity key changed") if (error.message !== "Identity key changed")
registerError(number, "Failed to reload device keys", error); registerError(number, "Failed to reload device keys", error);
else { else {
// TODO: Identity key changed, check which devices it changed for and get upset 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.",
registerError(number, "Identity key changed!!!!", error); textsecure.replay.SEND_MESSAGE, [number, getString(message.encode())]);
registerError(number, "Identity key changed", error);
} }
}); });
} else } else
@ -654,17 +707,13 @@ window.textsecure.sendMessage = function() {
var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); var devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number);
if (devicesForNumber.length == 0) { if (devicesForNumber.length == 0) {
getKeysForNumber(number).then(function() { getKeysForNumber(number)
devicesForNumber = textsecure.storage.devices.getDeviceObjectsForNumber(number); .then(reloadDevicesAndSend(number, true))
if (devicesForNumber.length == 0) .catch(function(error) {
registerError(number, "Failed to retreive new device keys for number " + number, null);
else
doSendMessage(number, devicesForNumber, message);
}).catch(function(error) {
registerError(number, "Failed to retreive new device keys for number " + number, error); registerError(number, "Failed to retreive new device keys for number " + number, error);
}); });
} else } else
doSendMessage(number, devicesForNumber, message); doSendMessage(number, devicesForNumber, true);
} }
} }
}(); }();

View file

@ -38,7 +38,6 @@
<div id="setup-complete" style="display: none;"> <div id="setup-complete" style="display: none;">
<h2>You are now registered on TextSecure with number <span id="complete-number"></span></h2> <h2>You are now registered on TextSecure with number <span id="complete-number"></span></h2>
</div> </div>
<script type="text/javascript" src="js-deps/nacl-common.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/jquery.js"></script>
<script type="text/javascript" src="js-deps/core.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/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.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/ProtoBuf.min.js"></script>
<script type="text/javascript" src="js/crypto.js"></script> <script type="text/javascript" src="js-deps/underscore.js"></script>
<script type="text/javascript" src="js/api.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/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/chromium.js"></script>
<script type="text/javascript" src="js/options.js"></script> <script type="text/javascript" src="js/options.js"></script>
</body> </body>

View file

@ -44,19 +44,8 @@
</form> </form>
</div> </div>
</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/nacl-common.js"></script>
<script type="text/javascript" src="js-deps/jquery.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/core.js"></script>
<script type="text/javascript" src="js-deps/enc-base64.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/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/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.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/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/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/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> <script type="text/javascript" src="js/popup.js"></script>
</body> </body>
</html> </html>

View file

@ -29,20 +29,27 @@
<script type="text/javascript" src="js-deps/core.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/enc-base64.js"></script>
<script type="text/javascript" src="js-deps/cipher-core.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/aes.js"></script>
<script type="text/javascript" src="js-deps/mode-ctr-min.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/pad-nopadding.js"></script>
<script type="text/javascript" src="js-deps/hmac-sha256.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/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/Long.min.js"></script>
<script type="text/javascript" src="js-deps/ByteBuffer.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/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/webcrypto.js"></script>
<script type="text/javascript" src="js/crypto.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/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/fake_api.js"></script>
<script type="text/javascript" src="js/test.js"></script> <script type="text/javascript" src="js/test.js"></script>
</body> </body>