FINALLY report crypto, etc errors to console thanks to promises...

This commit is contained in:
Matt Corallo 2014-05-13 04:40:29 -04:00
parent a7de5e2159
commit caa363b929
5 changed files with 112 additions and 111 deletions

104
js/api.js
View file

@ -44,7 +44,7 @@ var API = new function() {
* do_auth: alternative to user/password where user/password are figured out automagically
* jsonData: JSON data sent in the request body
*/
this.doAjax = function doAjax(param) {
var doAjax = function(param) {
if (param.urlParameters === undefined)
param.urlParameters = "";
@ -53,47 +53,52 @@ var API = new function() {
param.password = storage.getEncrypted("password");
}
$.ajax(URL_BASE + URL_CALLS[param.call] + param.urlParameters, {
type : param.httpType,
data : param.jsonData && jsonThing(param.jsonData),
contentType : 'application/json; charset=utf-8',
dataType : 'json',
return new Promise(function(resolve, reject) {
$.ajax(URL_BASE + URL_CALLS[param.call] + param.urlParameters, {
type : param.httpType,
data : param.jsonData && jsonThing(param.jsonData),
contentType : 'application/json; charset=utf-8',
dataType : 'json',
beforeSend : function(xhr) {
if (param.user !== undefined &&
param.password !== undefined)
xhr.setRequestHeader("Authorization", "Basic " + btoa(getString(param.user) + ":" + getString(param.password)));
},
beforeSend : function(xhr) {
if (param.user !== undefined &&
param.password !== undefined)
xhr.setRequestHeader("Authorization", "Basic " + btoa(getString(param.user) + ":" + getString(param.password)));
},
success : function(response, textStatus, jqXHR) {
if (param.success_callback !== undefined)
param.success_callback(response);
},
success : function(response, textStatus, jqXHR) {
resolve(response);
},
error : function(jqXHR, textStatus, errorThrown) {
var code = jqXHR.status;
if (code == 200) {
// happens sometimes when we get no response
// (TODO: Fix server to return 204? instead)
if (param.success_callback !== undefined)
param.success_callback(null);
return;
error : function(jqXHR, textStatus, errorThrown) {
var code = jqXHR.status;
if (code == 200) {
// happens sometimes when we get no response
// (TODO: Fix server to return 204? instead)
resolve(null);
return;
}
if (code > 999 || code < 100)
code = -1;
var e = new Error(code);
e.name = "HTTPError";
reject(e);
}
if (code > 999 || code < 100)
code = -1;
if (param.error_callback !== undefined)
param.error_callback(code);
}
});
});
};
this.requestVerificationCode = function(number, success_callback, error_callback) {
this.doAjax({
doAjax({
call : 'accounts',
httpType : 'GET',
urlParameters : '/sms/code/' + number,
success_callback : success_callback,
error_callback : error_callback
}).then(function(response) {
if (success_callback !== undefined)
success_callback(response);
}).catch(function(code) {
if (error_callback !== undefined)
error_callback(code);
});
};
@ -103,7 +108,7 @@ var API = new function() {
var call = single_device ? 'accounts' : 'devices';
var urlPrefix = single_device ? '/code/' : '/';
API.doAjax({
doAjax({
call : call,
httpType : 'PUT',
urlParameters : urlPrefix + code,
@ -112,8 +117,12 @@ var API = new function() {
jsonData : { signalingKey : btoa(getString(signaling_key)),
supportsSms : false,
fetchesMessages : true },
success_callback : success_callback,
error_callback : error_callback
}).then(function(response) {
if (success_callback !== undefined)
success_callback(response);
}).catch(function(code) {
if (error_callback !== undefined)
error_callback(code);
});
};
@ -123,23 +132,27 @@ var API = new function() {
for (var i = 0; i < keys.keys.length; i++)
keys.keys[i] = {keyId: i, publicKey: btoa(getString(keys.keys[i].publicKey)), identityKey: identityKey};
keys.lastResortKey = {keyId: keys.lastResortKey.keyId, publicKey: btoa(getString(keys.lastResortKey.publicKey)), identityKey: identityKey};
this.doAjax({
doAjax({
call : 'keys',
httpType : 'PUT',
do_auth : true,
jsonData : keys,
success_callback : success_callback,
error_callback : error_callback
}).then(function(response) {
if (success_callback !== undefined)
success_callback(response);
}).catch(function(code) {
if (error_callback !== undefined)
error_callback(code);
});
};
this.getKeysForNumber = function(number, success_callback, error_callback) {
this.doAjax({
doAjax({
call : 'keys',
httpType : 'GET',
do_auth : true,
urlParameters : "/" + getNumberFromString(number) + "/*",
success_callback : function(response) {
}).then(function(response) {
//TODO: Do this conversion somewhere else?
var res = response.keys;
for (var i = 0; i < res.length; i++) {
@ -149,12 +162,13 @@ var API = new function() {
res[i].keyId = 0;
}
success_callback(res);
},
error_callback : error_callback
}).catch(function(code) {
if (error_callback !== undefined)
error_callback(code);
});
};
this.sendMessages = function(destination, messageArray, success_callback, error_callback) {
this.sendMessages = function(destination, messageArray) {
//TODO: Do this conversion somewhere else?
for (var i = 0; i < messageArray.length; i++)
messageArray[i].body = btoa(messageArray[i].body);
@ -162,14 +176,12 @@ var API = new function() {
if (messageArray[0].relay !== undefined)
jsonData.relay = messageArray[0].relay;
this.doAjax({
return doAjax({
call : 'messages',
httpType : 'PUT',
urlParameters : '/' + destination,
do_auth : true,
jsonData : jsonData,
success_callback : success_callback,
error_callback : error_callback
});
};
}
}(); // API

View file

@ -115,16 +115,20 @@ window.crypto = (function() {
if (privKey === undefined || privKey.byteLength != 32)
throw new Error("Invalid private key");
if (pubKey === undefined || pubKey.byteLength != 33 || new Uint8Array(pubKey)[0] != 5)
if (pubKey === undefined || ((pubKey.byteLength != 33 || new Uint8Array(pubKey)[0] != 5) && pubKey.byteLength != 32))
throw new Error("Invalid public key");
if (pubKey.byteLength == 33)
pubKey = pubKey.slice(1);
else
console.error("WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey");
return new Promise(function(resolve) {
if (USE_NACL) {
postNaclMessage({command: "ECDHE", priv: privKey, pub: pubKey.slice(1)}).then(function(message) {
postNaclMessage({command: "ECDHE", priv: privKey, pub: pubKey}).then(function(message) {
resolve(message.res);
});
} else {
resolve(toArrayBuffer(curve25519(new Uint16Array(privKey), new Uint16Array(pubKey.slice(1)))));
resolve(toArrayBuffer(curve25519(new Uint16Array(privKey), new Uint16Array(pubKey))));
}
});
}
@ -438,7 +442,9 @@ window.crypto = (function() {
if (session === undefined) {
return createNewKeyPair(false).then(function(baseKey) {
preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey);
return initSession(true, baseKey, deviceObject.encodedNumber, deviceObject.identityKey, deviceObject.publicKey).then(function() {
return initSession(true, baseKey, deviceObject.encodedNumber,
toArrayBuffer(deviceObject.identityKey), toArrayBuffer(deviceObject.publicKey))
.then(function() {
//TODO: Delete preKey info on first message received back
session = crypto_storage.getSession(deviceObject.encodedNumber);
session.pendingPreKey = baseKey.pubKey;
@ -475,25 +481,27 @@ window.crypto = (function() {
var keys = {};
keys.keys = [];
var keysLeft = GENERATE_KEYS_KEYS_GENERATED;
return new Promise(function(resolve) {
for (var i = firstKeyId; i < firstKeyId + GENERATE_KEYS_KEYS_GENERATED; i++) {
crypto_storage.getNewPubKeySTORINGPrivKey("preKey" + i, false).then(function(pubKey) {
keys.keys[i] = {keyId: i, publicKey: pubKey, identityKey: identityKey};
keysLeft--;
if (keysLeft == 0) {
// 0xFFFFFF == 16777215
keys.lastResortKey = {keyId: 16777215, publicKey: crypto_storage.getStoredPubKey("preKey16777215"), identityKey: identityKey};//TODO: Rotate lastResortKey
if (keys.lastResortKey.publicKey === undefined) {
return crypto_storage.getNewPubKeySTORINGPrivKey("preKey16777215", false).then(function(pubKey) {
keys.lastResortKey.publicKey = pubKey;
resolve(keys);
});
} else
resolve(keys);
}
var generateKey = function(keyId) {
return crypto_storage.getNewPubKeySTORINGPrivKey("preKey" + keyId, false).then(function(pubKey) {
keys.keys[keyId] = {keyId: keyId, publicKey: pubKey, identityKey: identityKey};
});
};
var promises = [];
for (var i = firstKeyId; i < firstKeyId + GENERATE_KEYS_KEYS_GENERATED; i++)
promises[i] = generateKey(i);
return Promise.all(promises).then(function() {
// 0xFFFFFF == 16777215
keys.lastResortKey = {keyId: 16777215, publicKey: crypto_storage.getStoredPubKey("preKey16777215"), identityKey: identityKey};//TODO: Rotate lastResortKey
if (keys.lastResortKey.publicKey === undefined) {
return crypto_storage.getNewPubKeySTORINGPrivKey("preKey16777215", false).then(function(pubKey) {
keys.lastResortKey.publicKey = pubKey;
return keys;
});
}
} else
return keys;
});
}
if (identityKey === undefined)

View file

@ -14,14 +14,14 @@
*/
var FakeWhisperAPI = function() {
this.doAjax = function(param) {
var doAjax = function(param) {
if (param.success_callback) {
setTimeout(param.success_callback, 100, param.response);
}
}
this.getKeysForNumber = function(number, success_callback, error_callback) {
this.doAjax({ success_callback : success_callback,
doAjax({ success_callback : success_callback,
response : [{ identityKey : 1,
deviceId : 1,
publicKey : 1,
@ -30,7 +30,7 @@ var FakeWhisperAPI = function() {
}
this.sendMessages = function(jsonData, success_callback, error_callback) {
this.doAjax({ success_callback : success_callback,
doAjax({ success_callback : success_callback,
response : { missingDeviceIds: [] }
});
}

View file

@ -181,10 +181,6 @@ function jsonThing(thing) {
return JSON.stringify(ensureStringed(thing));
}
function getArrayBuffer(string) {
return base64DecToArr(btoa(string));
}
function base64ToArrayBuffer(string) {
return base64DecToArr(string);
}
@ -505,20 +501,10 @@ function getKeysForNumber(number, success_callback, error_callback) {
function sendMessageToDevices(number, deviceObjectList, message, success_callback, error_callback) {
var jsonData = [];
var relay = undefined;
var promises = [];
var doSend = function() {
API.sendMessages(number, jsonData,
function(result) {
success_callback(result);
}, function(code) {
error_callback(code);
}
);
}
var addEncryptionFor;
addEncryptionFor = function(i) {
crypto.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
var addEncryptionFor = function(i) {
return crypto.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
jsonData[i] = {
type: encryptedMsg.type,
destination: deviceObjectList[i].encodedNumber,
@ -531,27 +517,21 @@ function sendMessageToDevices(number, deviceObjectList, message, success_callbac
jsonData[i].relay = deviceObjectList[i].relay;
if (relay === undefined)
relay = jsonData[i].relay;
else if (relay != jsonData[i].relay) {
error_callback("Mismatched relays for number " + number);
return;
}
else if (relay != jsonData[i].relay)
throw new Error("Mismatched relays for number " + number);
} else {
if (relay === undefined)
relay = "";
else if (relay != "") {
error_callback("Mismatched relays for number " + number);
return;
}
else if (relay != "")
throw new Error("Mismatched relays for number " + number);
}
if (i+1 < deviceObjectList.length)
addEncryptionFor(i+1);
else
doSend();
});
//TODO: need to encrypt with session key?
}
addEncryptionFor(0);
for (var i = 0; i < deviceObjectList.length; i++)
promises[i] = addEncryptionFor(i);
return Promise.all(promises).then(function() {
return API.sendMessages(number, jsonData);
});
}
// callback(success/failure map, see code)
@ -573,13 +553,14 @@ function sendMessageToNumbers(numbers, message, callback) {
}
var doSendMessage = function(number, devicesForNumber, message) {
sendMessageToDevices(number, devicesForNumber, message, function(result) {
return sendMessageToDevices(number, devicesForNumber, message).then(function(result) {
successfulNumbers[successfulNumbers.length] = number;
numberCompleted();
}, function(error_code) {
//TODO: Re-request keys for number here
if (error_code == 410 || error_code == 409) {}
registerError(number, message);
}).catch(function(error) {
if (error instanceof Error && error.name == "HTTPError" && (error.message == 410 || error.message == 409)) {
//TODO: Re-request keys for number here
}
registerError(number, error);
});
}
@ -591,11 +572,11 @@ function sendMessageToNumbers(numbers, message, callback) {
getKeysForNumber(number, function(identity_key) {
devicesForNumber = getDeviceObjectListFromNumber(number);
if (devicesForNumber.length == 0)
registerError(number, "Failed to retreive new device keys for number " + number);
registerError(number, new Error("Failed to retreive new device keys for number " + number));
else
doSendMessage(number, devicesForNumber, message);
}, function(error_msg) {
registerError(number, "Failed to retreive new device keys for number " + number);
registerError(number, new Error("Failed to retreive new device keys for number " + number));
});
} else
doSendMessage(number, devicesForNumber, message);

View file

@ -85,7 +85,7 @@ $('#init-go').click(function() {
var register_keys_func = function() {
$('#verify2done').html('done');
crypto.generateKeys(function(keys) {
crypto.generateKeys().then(function(keys) {
$('#verify3done').html('done');
API.registerKeys(keys,
function(response) {