Update libaxolotl

// FREEBIE
This commit is contained in:
lilia 2016-03-11 16:23:37 -08:00
parent 151b9797f6
commit 7dff9ac51b
2 changed files with 388 additions and 316 deletions

View file

@ -34148,112 +34148,54 @@ axolotlInternal.crypto = function() {
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var axolotlInternal = axolotlInternal || {};
axolotlInternal.utils = function() { var util = (function() {
'use strict'; 'use strict';
var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
var StaticArrayBufferProto = new ArrayBuffer().__proto__; var StaticArrayBufferProto = new ArrayBuffer().__proto__;
var StaticUint8ArrayProto = new Uint8Array().__proto__; var StaticUint8ArrayProto = new Uint8Array().__proto__;
function stringObject(thing) { function stringObject(thing) {
if (thing === Object(thing)) { if (typeof thing === 'string') {
if (thing.__proto__ == StaticUint8ArrayProto)
return String.fromCharCode.apply(null, thing);
if (thing.__proto__ == StaticArrayBufferProto)
return getString(new Uint8Array(thing));
if (thing.__proto__ == StaticByteBufferProto)
return thing.toString("binary");
}
throw new Error("unsure how to stringify object of type " + typeof thing);
}
function isStringable(thing) {
return (thing === Object(thing) &&
(thing.__proto__ == StaticArrayBufferProto ||
thing.__proto__ == StaticUint8ArrayProto ||
thing.__proto__ == StaticByteBufferProto));
}
function ensureStringed(thing) {
if (typeof thing == "string" || typeof thing == "number" || typeof thing == "boolean")
return thing; return thing;
else if (isStringable(thing)) }
return stringObject(thing); if (thing === Object(thing)) {
else if (thing instanceof Array) { if (thing.__proto__ == StaticUint8ArrayProto) {
var res = []; return String.fromCharCode.apply(null, thing);
for (var i = 0; i < thing.length; i++) }
res[i] = ensureStringed(thing[i]); if (thing.__proto__ == StaticArrayBufferProto) {
return res; return stringObject(new Uint8Array(thing));
} else if (thing === Object(thing)) { }
var res = {}; if (thing.__proto__ == StaticByteBufferProto) {
for (var key in thing) return thing.toString('binary');
res[key] = ensureStringed(thing[key]); }
return res; }
} else if (thing === null) throw new Error('unsure how to stringify object of type ' + typeof thing);
return null;
else
throw new Error("unsure of how to jsonify object of type " + typeof thing);
} }
return { return {
stringObject: stringObject,
isStringable: function (thing) {
return (thing === Object(thing) &&
(thing.__proto__ == StaticArrayBufferProto ||
thing.__proto__ == StaticUint8ArrayProto ||
thing.__proto__ == StaticByteBufferProto));
},
isEqual: function(a, b) { isEqual: function(a, b) {
// TODO: Special-case arraybuffers, etc // TODO: Special-case arraybuffers, etc
if (a === undefined || b === undefined) if (a === undefined || b === undefined)
return false; return false;
a = getString(a); a = stringObject(a);
b = getString(b); b = stringObject(b);
var maxLength = Math.max(a.length, b.length); var maxLength = Math.max(a.length, b.length);
if (maxLength < 5) if (maxLength < 5)
throw new Error("a/b compare too short"); throw new Error("a/b compare too short");
return a.substring(0, Math.min(maxLength, a.length)) == b.substring(0, Math.min(maxLength, b.length)); return a.substring(0, Math.min(maxLength, a.length)) == b.substring(0, Math.min(maxLength, b.length));
}, }
jsonThing: function(thing) {
return JSON.stringify(ensureStringed(thing)); //TODO: jquery???
},
convertToString: function(thing) {
if (typeof thing == "string")
return thing;
else if (isStringable(thing))
return stringObject(thing);
else
throw new Error("Unsure how to convert object to string from type " + typeof thing);
},
convertToArrayBuffer: function(thing) {
if (thing === undefined)
return undefined;
if (thing === Object(thing)) {
if (thing.__proto__ == StaticArrayBufferProto)
return thing;
//TODO: Several more cases here...
}
if (thing instanceof Array) {
// Assuming Uint16Array from curve25519
//TODO: Move to convertToArrayBuffer
var res = new ArrayBuffer(thing.length * 2);
var uint = new Uint16Array(res);
for (var i = 0; i < thing.length; i++)
uint[i] = thing[i];
return res;
}
var str;
if (isStringable(thing))
str = stringObject(thing);
else if (typeof thing == "string")
str = thing;
else
throw new Error("Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer");
var res = new ArrayBuffer(str.length);
var uint = new Uint8Array(res);
for (var i = 0; i < str.length; i++)
uint[i] = str.charCodeAt(i);
return res;
},
}; };
}(); })();
/* vim: ts=4:sw=4 /* vim: ts=4:sw=4
* *
@ -34344,6 +34286,53 @@ window.axolotl.protocol = function(storage_interface) {
return count != 0; return count != 0;
} }
function toString(thing) {
if (typeof thing == 'string') {
return thing;
} else if (util.isStringable(thing)) {
return util.stringObject(thing);
} else {
throw new Error("Unsure how to convert object to string from type " + typeof thing);
}
}
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
function toArrayBuffer(thing) {
if (thing === undefined) {
return undefined;
}
if (thing === Object(thing)) {
if (thing.__proto__ == StaticArrayBufferProto)
return thing;
//TODO: Several more cases here...
}
if (thing instanceof Array) {
// Assuming Uint16Array from curve25519
//TODO: Move to convertToArrayBuffer
var res = new ArrayBuffer(thing.length * 2);
var uint = new Uint16Array(res);
for (var i = 0; i < thing.length; i++)
uint[i] = thing[i];
return res;
}
var str;
if (util.isStringable(thing)) {
str = util.stringObject(thing);
} else if (typeof thing == "string") {
str = thing;
} else {
throw new Error("Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer");
}
var res = new ArrayBuffer(str.length);
var uint = new Uint8Array(res);
for (var i = 0; i < str.length; i++) {
uint[i] = str.charCodeAt(i);
}
return res;
}
/*************************** /***************************
*** Key/session storage *** *** Key/session storage ***
***************************/ ***************************/
@ -34364,13 +34353,13 @@ window.axolotl.protocol = function(storage_interface) {
if (registrationId === undefined) if (registrationId === undefined)
throw new Error("Tried to save a session for an existing device that didn't exist"); throw new Error("Tried to save a session for an existing device that didn't exist");
else else
record = new axolotlInternal.RecipientRecord(session.indexInfo.remoteIdentityKey, registrationId); record = new axolotlInternal.RecipientRecord(toString(session.indexInfo.remoteIdentityKey), registrationId);
} }
var sessions = record._sessions; var sessions = record._sessions;
if (record.identityKey === null) if (record.identityKey === null)
record.identityKey = session.indexInfo.remoteIdentityKey; record.identityKey = session.indexInfo.remoteIdentityKey;
if (axolotlInternal.utils.convertToString(record.identityKey) !== axolotlInternal.utils.convertToString(session.indexInfo.remoteIdentityKey)) { if (toString(record.identityKey) !== toString(session.indexInfo.remoteIdentityKey)) {
var e = new Error("Identity key changed at session save time"); var e = new Error("Identity key changed at session save time");
e.identityKey = session.indexInfo.remoteIdentityKey.toArrayBuffer(); e.identityKey = session.indexInfo.remoteIdentityKey.toArrayBuffer();
throw e; throw e;
@ -34395,9 +34384,9 @@ window.axolotl.protocol = function(storage_interface) {
} }
if (doDeleteSession) if (doDeleteSession)
delete sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)]; delete sessions[toString(session.indexInfo.baseKey)];
else else
sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)] = session; sessions[toString(session.indexInfo.baseKey)] = session;
var openSessionRemaining = false; var openSessionRemaining = false;
for (var key in sessions) for (var key in sessions)
@ -34411,7 +34400,7 @@ window.axolotl.protocol = function(storage_interface) {
throw new Error("Had open sessions on a record that had no registrationId set"); throw new Error("Had open sessions on a record that had no registrationId set");
return storage_interface.getIdentityKey(encodedNumber).then(function(identityKey) { return storage_interface.getIdentityKey(encodedNumber).then(function(identityKey) {
if (identityKey !== undefined && axolotlInternal.utils.convertToString(identityKey) !== axolotlInternal.utils.convertToString(record.identityKey)) if (identityKey !== undefined && toString(identityKey) !== toString(record.identityKey))
throw new Error("Tried to change identity key at save time"); throw new Error("Tried to change identity key at save time");
return storage_interface.putIdentityKey(encodedNumber, record.identityKey).then(function() { return storage_interface.putIdentityKey(encodedNumber, record.identityKey).then(function() {
@ -34462,7 +34451,7 @@ window.axolotl.protocol = function(storage_interface) {
detectDuplicateOpenSessions(sessions, encodedNumber); detectDuplicateOpenSessions(sessions, encodedNumber);
var searchKey = axolotlInternal.utils.convertToString(remoteEphemeralKey); var searchKey = toString(remoteEphemeralKey);
var openSession = undefined; var openSession = undefined;
for (var key in sessions) { for (var key in sessions) {
@ -34490,7 +34479,7 @@ window.axolotl.protocol = function(storage_interface) {
} }
var sessions = record._sessions; var sessions = record._sessions;
var preferredSession = record._sessions[axolotlInternal.utils.convertToString(baseKey)]; var preferredSession = record._sessions[toString(baseKey)];
if (preferredSession !== undefined) if (preferredSession !== undefined)
return preferredSession; return preferredSession;
@ -34511,7 +34500,7 @@ window.axolotl.protocol = function(storage_interface) {
if (salt.byteLength != 32) if (salt.byteLength != 32)
throw new Error("Got salt of incorrect length"); throw new Error("Got salt of incorrect length");
info = axolotlInternal.utils.convertToArrayBuffer(info); // TODO: maybe convert calls? info = toArrayBuffer(info); // TODO: maybe convert calls?
return axolotlInternal.crypto.HKDF(input, salt, info); return axolotlInternal.crypto.HKDF(input, salt, info);
} }
@ -34539,12 +34528,19 @@ window.axolotl.protocol = function(storage_interface) {
var calculateRatchet = function(session, remoteKey, sending) { var calculateRatchet = function(session, remoteKey, sending) {
var ratchet = session.currentRatchet; var ratchet = session.currentRatchet;
return axolotlInternal.crypto.ECDHE(remoteKey, axolotlInternal.utils.convertToArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) { return axolotlInternal.crypto.ECDHE(remoteKey, toArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) {
return HKDF(sharedSecret, axolotlInternal.utils.convertToArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) { return HKDF(sharedSecret, toArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) {
if (sending) var ephemeralPublicKey;
session[axolotlInternal.utils.convertToString(ratchet.ephemeralKeyPair.pubKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } }; if (sending) {
else ephemeralPublicKey = ratchet.ephemeralKeyPair.pubKey;
session[axolotlInternal.utils.convertToString(remoteKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } }; }
else {
ephemeralPublicKey = remoteKey;
}
session[toString(ephemeralPublicKey)] = {
messageKeys: {},
chainKey: { counter: -1, key: masterKey[1] }
};
ratchet.rootKey = masterKey[0]; ratchet.rootKey = masterKey[0];
}); });
}); });
@ -34634,7 +34630,7 @@ window.axolotl.protocol = function(storage_interface) {
var newList = []; var newList = [];
for (var i = 0; i < session.oldRatchetList.length; i++) { for (var i = 0; i < session.oldRatchetList.length; i++) {
var entry = session.oldRatchetList[i]; var entry = session.oldRatchetList[i];
var ratchet = axolotlInternal.utils.convertToString(entry.ephemeralKey); var ratchet = toString(entry.ephemeralKey);
console.log("Checking old chain with added time " + (entry.added/1000)); console.log("Checking old chain with added time " + (entry.added/1000));
if ((!objectContainsKeys(session[ratchet].messageKeys) && (session[ratchet].chainKey === undefined || session[ratchet].chainKey.key === undefined)) if ((!objectContainsKeys(session[ratchet].messageKeys) && (session[ratchet].chainKey === undefined || session[ratchet].chainKey.key === undefined))
|| entry.added < Date.now() - MESSAGE_LOST_THRESHOLD_MS) { || entry.added < Date.now() - MESSAGE_LOST_THRESHOLD_MS) {
@ -34655,7 +34651,7 @@ window.axolotl.protocol = function(storage_interface) {
// but we cannot send messages or step the ratchet // but we cannot send messages or step the ratchet
// Delete current sending ratchet // Delete current sending ratchet
delete session[axolotlInternal.utils.convertToString(session.currentRatchet.ephemeralKeyPair.pubKey)]; delete session[toString(session.currentRatchet.ephemeralKeyPair.pubKey)];
// Move all receive ratchets to the oldRatchetList to mark them for deletion // Move all receive ratchets to the oldRatchetList to mark them for deletion
for (var i in session) { for (var i in session) {
if (session[i].chainKey !== undefined && session[i].chainKey.key !== undefined) { if (session[i].chainKey !== undefined && session[i].chainKey.key !== undefined) {
@ -34685,7 +34681,7 @@ window.axolotl.protocol = function(storage_interface) {
var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) { var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) {
return storage_interface.getPreKey(message.preKeyId).then(function(preKeyPair) { return storage_interface.getPreKey(message.preKeyId).then(function(preKeyPair) {
return storage_interface.getSignedPreKey(message.signedPreKeyId).then(function(signedPreKeyPair) { return storage_interface.getSignedPreKey(message.signedPreKeyId).then(function(signedPreKeyPair) {
return crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.baseKey)).then(function(session) { return crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, toArrayBuffer(message.baseKey)).then(function(session) {
return crypto_storage.getOpenSession(encodedNumber).then(function(open_session) { return crypto_storage.getOpenSession(encodedNumber).then(function(open_session) {
if (signedPreKeyPair === undefined) { if (signedPreKeyPair === undefined) {
// Session may or may not be the right one, but if its not, we can't do anything about it // Session may or may not be the right one, but if its not, we can't do anything about it
@ -34697,13 +34693,13 @@ window.axolotl.protocol = function(storage_interface) {
} }
if (session !== undefined) { if (session !== undefined) {
// Duplicate PreKeyMessage for session: // Duplicate PreKeyMessage for session:
if (axolotlInternal.utils.isEqual(session.indexInfo.baseKey, message.baseKey)) if (util.isEqual(session.indexInfo.baseKey, message.baseKey))
return Promise.resolve([session, undefined]); return Promise.resolve([session, undefined]);
// We already had a session/known identity key: // We already had a session/known identity key:
if (axolotlInternal.utils.isEqual(session.indexInfo.remoteIdentityKey, message.identityKey)) { if (util.isEqual(session.indexInfo.remoteIdentityKey, message.identityKey)) {
// If the identity key matches the previous one, close the previous one and use the new one // If the identity key matches the previous one, close the previous one and use the new one
if (open_session !== undefined) if (open_session !== undefin)
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
@ -34712,7 +34708,7 @@ window.axolotl.protocol = function(storage_interface) {
throw e; throw e;
} }
} }
return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.identityKey), axolotlInternal.utils.convertToArrayBuffer(message.baseKey), undefined) return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, toArrayBuffer(message.identityKey), toArrayBuffer(message.baseKey), undefined)
.then(function(new_session) { .then(function(new_session) {
// Note that the session is not actually saved until the very end of decryptWhisperMessage // Note that the session is not actually saved until the very end of decryptWhisperMessage
// ... to ensure that the sender actually holds the private keys for all reported pubkeys // ... to ensure that the sender actually holds the private keys for all reported pubkeys
@ -34741,7 +34737,7 @@ window.axolotl.protocol = function(storage_interface) {
if (chain.chainKey.key === undefined) if (chain.chainKey.key === undefined)
throw new Error("Got invalid request to extend chain after it was already closed"); throw new Error("Got invalid request to extend chain after it was already closed");
var key = axolotlInternal.utils.convertToArrayBuffer(chain.chainKey.key); var key = toArrayBuffer(chain.chainKey.key);
var byteArray = new Uint8Array(1); var byteArray = new Uint8Array(1);
byteArray[0] = 1; byteArray[0] = 1;
return axolotlInternal.crypto.sign(key, byteArray.buffer).then(function(mac) { return axolotlInternal.crypto.sign(key, byteArray.buffer).then(function(mac) {
@ -34756,7 +34752,7 @@ window.axolotl.protocol = function(storage_interface) {
} }
var maybeStepRatchet = function(session, remoteKey, previousCounter) { var maybeStepRatchet = function(session, remoteKey, previousCounter) {
if (session[axolotlInternal.utils.convertToString(remoteKey)] !== undefined) if (session[toString(remoteKey)] !== undefined)
return Promise.resolve(); return Promise.resolve();
var ratchet = session.currentRatchet; var ratchet = session.currentRatchet;
@ -34764,7 +34760,7 @@ window.axolotl.protocol = function(storage_interface) {
var finish = function() { var finish = function() {
return calculateRatchet(session, remoteKey, false).then(function() { return calculateRatchet(session, remoteKey, false).then(function() {
// Now swap the ephemeral key and calculate the new sending chain // Now swap the ephemeral key and calculate the new sending chain
var previousRatchet = axolotlInternal.utils.convertToString(ratchet.ephemeralKeyPair.pubKey); var previousRatchet = toString(ratchet.ephemeralKeyPair.pubKey);
if (session[previousRatchet] !== undefined) { if (session[previousRatchet] !== undefined) {
ratchet.previousCounter = session[previousRatchet].chainKey.counter; ratchet.previousCounter = session[previousRatchet].chainKey.counter;
delete session[previousRatchet]; delete session[previousRatchet];
@ -34779,12 +34775,12 @@ window.axolotl.protocol = function(storage_interface) {
}); });
} }
var previousRatchet = session[axolotlInternal.utils.convertToString(ratchet.lastRemoteEphemeralKey)]; var previousRatchet = session[toString(ratchet.lastRemoteEphemeralKey)];
if (previousRatchet !== undefined) { if (previousRatchet !== undefined) {
return fillMessageKeys(previousRatchet, previousCounter).then(function() { return fillMessageKeys(previousRatchet, previousCounter).then(function() {
delete previousRatchet.chainKey.key; delete previousRatchet.chainKey.key;
if (!objectContainsKeys(previousRatchet.messageKeys)) if (!objectContainsKeys(previousRatchet.messageKeys))
delete session[axolotlInternal.utils.convertToString(ratchet.lastRemoteEphemeralKey)]; delete session[toString(ratchet.lastRemoteEphemeralKey)];
else else
session.oldRatchetList[session.oldRatchetList.length] = { added: Date.now(), ephemeralKey: ratchet.lastRemoteEphemeralKey }; session.oldRatchetList[session.oldRatchetList.length] = { added: Date.now(), ephemeralKey: ratchet.lastRemoteEphemeralKey };
}).then(finish); }).then(finish);
@ -34793,16 +34789,18 @@ window.axolotl.protocol = function(storage_interface) {
} }
var doDecryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) { var doDecryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) {
if (messageBytes[0] != String.fromCharCode((3 << 4) | 3)) if (!messageBytes instanceof ArrayBuffer) {
throw new Error("Expected messageBytes to be an ArrayBuffer");
}
var version = (new Uint8Array(messageBytes))[0];
if (version !== ((3 << 4) | 3)) {
throw new Error("Bad version number on WhisperMessage"); throw new Error("Bad version number on WhisperMessage");
}
var messageProto = messageBytes.slice(1, messageBytes.byteLength- 8);
var mac = messageBytes.slice(messageBytes.byteLength - 8, messageBytes.byteLength);
var messageProto = messageBytes.substring(1, messageBytes.length - 8); var message = axolotlInternal.protobuf.WhisperMessage.decode(messageProto);
var mac = axolotlInternal.utils.convertToArrayBuffer( var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer();
messageBytes.substring(messageBytes.length - 8, messageBytes.length)
);
var message = axolotlInternal.protobuf.WhisperMessage.decode(messageProto, 'binary');
var remoteEphemeralKey = axolotlInternal.utils.convertToArrayBuffer(message.ephemeralKey);
var promise; var promise;
if (session === undefined) { if (session === undefined) {
@ -34817,7 +34815,7 @@ window.axolotl.protocol = function(storage_interface) {
return promise.then(function(session) { return promise.then(function(session) {
return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() { return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() {
var chain = session[axolotlInternal.utils.convertToString(message.ephemeralKey)]; var chain = session[toString(message.ephemeralKey)];
return fillMessageKeys(chain, message.counter).then(function() { return fillMessageKeys(chain, message.counter).then(function() {
var messageKey = chain.messageKeys[message.counter]; var messageKey = chain.messageKeys[message.counter];
@ -34826,21 +34824,22 @@ window.axolotl.protocol = function(storage_interface) {
e.name = 'MessageCounterError'; e.name = 'MessageCounterError';
throw e; throw e;
} }
return HKDF(axolotlInternal.utils.convertToArrayBuffer(messageKey), '', "WhisperMessageKeys").then(function(keys) { return HKDF(toArrayBuffer(messageKey), '', "WhisperMessageKeys").then(function(keys) {
return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) { return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
delete chain.messageKeys[message.counter]; delete chain.messageKeys[message.counter];
var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto); var macInput = new Uint8Array(messageProto.byteLength + 33*2 + 1);
var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1); macInput.set(new Uint8Array(toArrayBuffer(session.indexInfo.remoteIdentityKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey))); macInput.set(new Uint8Array(toArrayBuffer(ourIdentityKey.pubKey)), 33);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1); macInput.set(new Uint8Array(messageProto), 33*2 + 1);
return verifyMAC(macInput.buffer, keys[1], mac, 8).then(function() { return verifyMAC(macInput.buffer, keys[1], mac, 8).then(function() {
return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16)) return axolotlInternal.crypto.decrypt(
.then(function(paddedPlaintext) { keys[0],
message.ciphertext.toArrayBuffer(),
keys[2].slice(0, 16)
).then(function(paddedPlaintext) {
paddedPlaintext = new Uint8Array(paddedPlaintext); paddedPlaintext = new Uint8Array(paddedPlaintext);
var plaintext; var plaintext;
for (var i = paddedPlaintext.length - 1; i >= 0; i--) { for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
@ -34877,15 +34876,20 @@ window.axolotl.protocol = function(storage_interface) {
//TODO: SHARP EDGE HERE //TODO: SHARP EDGE HERE
//XXX: Also, you MUST call the session close function before processing another message....except its a promise...so you literally cant! //XXX: Also, you MUST call the session close function before processing another message....except its a promise...so you literally cant!
// returns decrypted plaintext and a function that must be called if the message indicates session close // returns decrypted plaintext and a function that must be called if the message indicates session close
self.decryptWhisperMessage = function(encodedNumber, messageBytes, session) { self.decryptWhisperMessage = function(encodedNumber, messageBytes) {
return doDecryptWhisperMessage(encodedNumber, messageBytes, session); return doDecryptWhisperMessage(encodedNumber, toArrayBuffer(messageBytes));
} };
// Inits a session (maybe) and then decrypts the message // Inits a session (maybe) and then decrypts the message
self.handlePreKeyWhisperMessage = function(from, encodedMessage, encoding) { self.handlePreKeyWhisperMessage = function(from, encodedMessage, encoding) {
var preKeyProto = axolotlInternal.protobuf.PreKeyWhisperMessage.decode(encodedMessage, encoding); var preKeyProto = axolotlInternal.protobuf.PreKeyWhisperMessage.decode(encodedMessage, encoding);
return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) { return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) {
return doDecryptWhisperMessage(from, axolotlInternal.utils.convertToString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) { return doDecryptWhisperMessage(
from,
preKeyProto.message.toArrayBuffer(),
sessions[0],
preKeyProto.registrationId
).then(function(result) {
if (sessions[1] !== undefined) if (sessions[1] !== undefined)
return sessions[1]().then(function() { return result; }); return sessions[1]().then(function() { return result; });
return result; return result;
@ -34921,22 +34925,22 @@ window.axolotl.protocol = function(storage_interface) {
paddedPlaintext.set(new Uint8Array(plaintext)); paddedPlaintext.set(new Uint8Array(plaintext));
paddedPlaintext[plaintext.byteLength] = 0x80; paddedPlaintext[plaintext.byteLength] = 0x80;
msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey); msg.ephemeralKey = toArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)]; var chain = session[toString(msg.ephemeralKey)];
return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() { return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) { return HKDF(toArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[chain.chainKey.counter]; delete chain.messageKeys[chain.chainKey.counter];
msg.counter = chain.chainKey.counter; msg.counter = chain.chainKey.counter;
msg.previousCounter = session.currentRatchet.previousCounter; msg.previousCounter = session.currentRatchet.previousCounter;
return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) { return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
msg.ciphertext = ciphertext; msg.ciphertext = ciphertext;
var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode()); var encodedMsg = toArrayBuffer(msg.encode());
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1); var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey))); macInput.set(new Uint8Array(toArrayBuffer(ourIdentityKey.pubKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33); macInput.set(new Uint8Array(toArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1); macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
@ -34958,26 +34962,26 @@ window.axolotl.protocol = function(storage_interface) {
} }
var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage(); var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage();
preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey); preKeyMsg.identityKey = toArrayBuffer(ourIdentityKey.pubKey);
preKeyMsg.registrationId = myRegistrationId; preKeyMsg.registrationId = myRegistrationId;
if (session === undefined) { if (session === undefined) {
var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey); var deviceIdentityKey = toArrayBuffer(deviceObject.identityKey);
var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey); var deviceSignedKey = toArrayBuffer(deviceObject.signedKey);
return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() { return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, toArrayBuffer(deviceObject.signedKeySignature)).then(function() {
return axolotlInternal.crypto.createKeyPair().then(function(baseKey) { return axolotlInternal.crypto.createKeyPair().then(function(baseKey) {
preKeyMsg.preKeyId = deviceObject.preKeyId; preKeyMsg.preKeyId = deviceObject.preKeyId;
preKeyMsg.signedPreKeyId = deviceObject.signedKeyId; preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey); preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey);
return initSession(true, baseKey, undefined, return initSession(true, baseKey, undefined,
deviceObject.encodedNumber, deviceIdentityKey, deviceObject.encodedNumber, deviceIdentityKey,
axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey), toArrayBuffer(deviceObject.preKey),
deviceSignedKey).then(function(new_session) { deviceSignedKey).then(function(new_session) {
session = new_session; session = new_session;
session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey }; session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
return doEncryptPushMessageContent().then(function(message) { return doEncryptPushMessageContent().then(function(message) {
preKeyMsg.message = message; preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode()); var result = String.fromCharCode((3 << 4) | 3) + toString(preKeyMsg.encode());
return {type: 3, body: result}; return {type: 3, body: result};
}); });
}); });
@ -34986,15 +34990,15 @@ window.axolotl.protocol = function(storage_interface) {
} else } else
return doEncryptPushMessageContent().then(function(message) { return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) { if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey); preKeyMsg.baseKey = toArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId; preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId; preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message; preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode()); var result = String.fromCharCode((3 << 4) | 3) + toString(preKeyMsg.encode());
return {type: 3, body: result}; return {type: 3, body: result};
} else } else
return {type: 1, body: axolotlInternal.utils.convertToString(message)}; return {type: 1, body: toString(message)};
}); });
}); });
}); });
@ -35006,8 +35010,8 @@ window.axolotl.protocol = function(storage_interface) {
var keyPair; var keyPair;
socketInfo.decryptAndHandleDeviceInit = function(deviceInit) { socketInfo.decryptAndHandleDeviceInit = function(deviceInit) {
var masterEphemeral = axolotlInternal.utils.convertToArrayBuffer(deviceInit.publicKey); var masterEphemeral = toArrayBuffer(deviceInit.publicKey);
var message = axolotlInternal.utils.convertToArrayBuffer(deviceInit.body); var message = toArrayBuffer(deviceInit.body);
return axolotlInternal.crypto.ECDHE(masterEphemeral, keyPair.privKey).then(function(ecRes) { return axolotlInternal.crypto.ECDHE(masterEphemeral, keyPair.privKey).then(function(ecRes) {
return HKDF(ecRes, '', "TextSecure Provisioning Message").then(function(keys) { return HKDF(ecRes, '', "TextSecure Provisioning Message").then(function(keys) {
@ -35198,19 +35202,51 @@ var axolotlInternal = axolotlInternal || {};
axolotlInternal.RecipientRecord = function() { axolotlInternal.RecipientRecord = function() {
'use strict'; 'use strict';
function ensureStringed(thing) {
if (typeof thing == "string" || typeof thing == "number" || typeof thing == "boolean")
return thing;
else if (util.isStringable(thing))
return util.stringObject(thing);
else if (thing instanceof Array) {
var res = [];
for (var i = 0; i < thing.length; i++)
res[i] = ensureStringed(thing[i]);
return res;
} else if (thing === Object(thing)) {
var res = {};
for (var key in thing)
res[key] = ensureStringed(thing[key]);
return res;
} else if (thing === null)
return null;
else
throw new Error("unsure of how to jsonify object of type " + typeof thing);
}
function jsonThing(thing) {
return JSON.stringify(ensureStringed(thing)); //TODO: jquery???
}
var RecipientRecord = function(identityKey, registrationId) { var RecipientRecord = function(identityKey, registrationId) {
this._sessions = {}; this._sessions = {};
this.identityKey = identityKey !== undefined ? axolotlInternal.utils.convertToString(identityKey) : null; if (typeof identityKey !== 'string') {
throw new Error('RecipientRecord: Invalid identityKey');
}
this.identityKey = identityKey;
this.registrationId = registrationId; this.registrationId = registrationId;
if (this.registrationId === undefined || typeof this.registrationId !== "number") if (this.registrationId === undefined || typeof this.registrationId !== 'number') {
this.registrationId = null; this.registrationId = null;
}
}; };
RecipientRecord.prototype.serialize = function() { RecipientRecord.prototype.serialize = function() {
return axolotlInternal.utils.jsonThing({sessions: this._sessions, registrationId: this.registrationId, identityKey: this.identityKey}); return jsonThing({
} sessions : this._sessions,
registrationId : this.registrationId,
identityKey : this.identityKey
});
};
RecipientRecord.deserialize = function(serialized) { RecipientRecord.deserialize = function(serialized) {
var data = JSON.parse(serialized); var data = JSON.parse(serialized);
@ -35221,11 +35257,11 @@ axolotlInternal.RecipientRecord = function() {
if (record.identityKey === undefined || record.registrationId === undefined) if (record.identityKey === undefined || record.registrationId === undefined)
throw new Error("Error deserializing RecipientRecord"); throw new Error("Error deserializing RecipientRecord");
return record; return record;
} };
RecipientRecord.prototype.haveOpenSession = function() { RecipientRecord.prototype.haveOpenSession = function() {
return this.registrationId !== null; return this.registrationId !== null;
} };
return RecipientRecord; return RecipientRecord;
}(); }();

View file

@ -34034,112 +34034,54 @@ axolotlInternal.crypto = function() {
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var axolotlInternal = axolotlInternal || {};
axolotlInternal.utils = function() { var util = (function() {
'use strict'; 'use strict';
var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; var StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
var StaticArrayBufferProto = new ArrayBuffer().__proto__; var StaticArrayBufferProto = new ArrayBuffer().__proto__;
var StaticUint8ArrayProto = new Uint8Array().__proto__; var StaticUint8ArrayProto = new Uint8Array().__proto__;
function stringObject(thing) { function stringObject(thing) {
if (thing === Object(thing)) { if (typeof thing === 'string') {
if (thing.__proto__ == StaticUint8ArrayProto)
return String.fromCharCode.apply(null, thing);
if (thing.__proto__ == StaticArrayBufferProto)
return getString(new Uint8Array(thing));
if (thing.__proto__ == StaticByteBufferProto)
return thing.toString("binary");
}
throw new Error("unsure how to stringify object of type " + typeof thing);
}
function isStringable(thing) {
return (thing === Object(thing) &&
(thing.__proto__ == StaticArrayBufferProto ||
thing.__proto__ == StaticUint8ArrayProto ||
thing.__proto__ == StaticByteBufferProto));
}
function ensureStringed(thing) {
if (typeof thing == "string" || typeof thing == "number" || typeof thing == "boolean")
return thing; return thing;
else if (isStringable(thing)) }
return stringObject(thing); if (thing === Object(thing)) {
else if (thing instanceof Array) { if (thing.__proto__ == StaticUint8ArrayProto) {
var res = []; return String.fromCharCode.apply(null, thing);
for (var i = 0; i < thing.length; i++) }
res[i] = ensureStringed(thing[i]); if (thing.__proto__ == StaticArrayBufferProto) {
return res; return stringObject(new Uint8Array(thing));
} else if (thing === Object(thing)) { }
var res = {}; if (thing.__proto__ == StaticByteBufferProto) {
for (var key in thing) return thing.toString('binary');
res[key] = ensureStringed(thing[key]); }
return res; }
} else if (thing === null) throw new Error('unsure how to stringify object of type ' + typeof thing);
return null;
else
throw new Error("unsure of how to jsonify object of type " + typeof thing);
} }
return { return {
stringObject: stringObject,
isStringable: function (thing) {
return (thing === Object(thing) &&
(thing.__proto__ == StaticArrayBufferProto ||
thing.__proto__ == StaticUint8ArrayProto ||
thing.__proto__ == StaticByteBufferProto));
},
isEqual: function(a, b) { isEqual: function(a, b) {
// TODO: Special-case arraybuffers, etc // TODO: Special-case arraybuffers, etc
if (a === undefined || b === undefined) if (a === undefined || b === undefined)
return false; return false;
a = getString(a); a = stringObject(a);
b = getString(b); b = stringObject(b);
var maxLength = Math.max(a.length, b.length); var maxLength = Math.max(a.length, b.length);
if (maxLength < 5) if (maxLength < 5)
throw new Error("a/b compare too short"); throw new Error("a/b compare too short");
return a.substring(0, Math.min(maxLength, a.length)) == b.substring(0, Math.min(maxLength, b.length)); return a.substring(0, Math.min(maxLength, a.length)) == b.substring(0, Math.min(maxLength, b.length));
}, }
jsonThing: function(thing) {
return JSON.stringify(ensureStringed(thing)); //TODO: jquery???
},
convertToString: function(thing) {
if (typeof thing == "string")
return thing;
else if (isStringable(thing))
return stringObject(thing);
else
throw new Error("Unsure how to convert object to string from type " + typeof thing);
},
convertToArrayBuffer: function(thing) {
if (thing === undefined)
return undefined;
if (thing === Object(thing)) {
if (thing.__proto__ == StaticArrayBufferProto)
return thing;
//TODO: Several more cases here...
}
if (thing instanceof Array) {
// Assuming Uint16Array from curve25519
//TODO: Move to convertToArrayBuffer
var res = new ArrayBuffer(thing.length * 2);
var uint = new Uint16Array(res);
for (var i = 0; i < thing.length; i++)
uint[i] = thing[i];
return res;
}
var str;
if (isStringable(thing))
str = stringObject(thing);
else if (typeof thing == "string")
str = thing;
else
throw new Error("Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer");
var res = new ArrayBuffer(str.length);
var uint = new Uint8Array(res);
for (var i = 0; i < str.length; i++)
uint[i] = str.charCodeAt(i);
return res;
},
}; };
}(); })();
/* vim: ts=4:sw=4 /* vim: ts=4:sw=4
* *
@ -34230,6 +34172,53 @@ window.axolotl.protocol = function(storage_interface) {
return count != 0; return count != 0;
} }
function toString(thing) {
if (typeof thing == 'string') {
return thing;
} else if (util.isStringable(thing)) {
return util.stringObject(thing);
} else {
throw new Error("Unsure how to convert object to string from type " + typeof thing);
}
}
var StaticArrayBufferProto = new ArrayBuffer().__proto__;
function toArrayBuffer(thing) {
if (thing === undefined) {
return undefined;
}
if (thing === Object(thing)) {
if (thing.__proto__ == StaticArrayBufferProto)
return thing;
//TODO: Several more cases here...
}
if (thing instanceof Array) {
// Assuming Uint16Array from curve25519
//TODO: Move to convertToArrayBuffer
var res = new ArrayBuffer(thing.length * 2);
var uint = new Uint16Array(res);
for (var i = 0; i < thing.length; i++)
uint[i] = thing[i];
return res;
}
var str;
if (util.isStringable(thing)) {
str = util.stringObject(thing);
} else if (typeof thing == "string") {
str = thing;
} else {
throw new Error("Tried to convert a non-stringable thing of type " + typeof thing + " to an array buffer");
}
var res = new ArrayBuffer(str.length);
var uint = new Uint8Array(res);
for (var i = 0; i < str.length; i++) {
uint[i] = str.charCodeAt(i);
}
return res;
}
/*************************** /***************************
*** Key/session storage *** *** Key/session storage ***
***************************/ ***************************/
@ -34250,13 +34239,13 @@ window.axolotl.protocol = function(storage_interface) {
if (registrationId === undefined) if (registrationId === undefined)
throw new Error("Tried to save a session for an existing device that didn't exist"); throw new Error("Tried to save a session for an existing device that didn't exist");
else else
record = new axolotlInternal.RecipientRecord(session.indexInfo.remoteIdentityKey, registrationId); record = new axolotlInternal.RecipientRecord(toString(session.indexInfo.remoteIdentityKey), registrationId);
} }
var sessions = record._sessions; var sessions = record._sessions;
if (record.identityKey === null) if (record.identityKey === null)
record.identityKey = session.indexInfo.remoteIdentityKey; record.identityKey = session.indexInfo.remoteIdentityKey;
if (axolotlInternal.utils.convertToString(record.identityKey) !== axolotlInternal.utils.convertToString(session.indexInfo.remoteIdentityKey)) { if (toString(record.identityKey) !== toString(session.indexInfo.remoteIdentityKey)) {
var e = new Error("Identity key changed at session save time"); var e = new Error("Identity key changed at session save time");
e.identityKey = session.indexInfo.remoteIdentityKey.toArrayBuffer(); e.identityKey = session.indexInfo.remoteIdentityKey.toArrayBuffer();
throw e; throw e;
@ -34281,9 +34270,9 @@ window.axolotl.protocol = function(storage_interface) {
} }
if (doDeleteSession) if (doDeleteSession)
delete sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)]; delete sessions[toString(session.indexInfo.baseKey)];
else else
sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)] = session; sessions[toString(session.indexInfo.baseKey)] = session;
var openSessionRemaining = false; var openSessionRemaining = false;
for (var key in sessions) for (var key in sessions)
@ -34297,7 +34286,7 @@ window.axolotl.protocol = function(storage_interface) {
throw new Error("Had open sessions on a record that had no registrationId set"); throw new Error("Had open sessions on a record that had no registrationId set");
return storage_interface.getIdentityKey(encodedNumber).then(function(identityKey) { return storage_interface.getIdentityKey(encodedNumber).then(function(identityKey) {
if (identityKey !== undefined && axolotlInternal.utils.convertToString(identityKey) !== axolotlInternal.utils.convertToString(record.identityKey)) if (identityKey !== undefined && toString(identityKey) !== toString(record.identityKey))
throw new Error("Tried to change identity key at save time"); throw new Error("Tried to change identity key at save time");
return storage_interface.putIdentityKey(encodedNumber, record.identityKey).then(function() { return storage_interface.putIdentityKey(encodedNumber, record.identityKey).then(function() {
@ -34348,7 +34337,7 @@ window.axolotl.protocol = function(storage_interface) {
detectDuplicateOpenSessions(sessions, encodedNumber); detectDuplicateOpenSessions(sessions, encodedNumber);
var searchKey = axolotlInternal.utils.convertToString(remoteEphemeralKey); var searchKey = toString(remoteEphemeralKey);
var openSession = undefined; var openSession = undefined;
for (var key in sessions) { for (var key in sessions) {
@ -34376,7 +34365,7 @@ window.axolotl.protocol = function(storage_interface) {
} }
var sessions = record._sessions; var sessions = record._sessions;
var preferredSession = record._sessions[axolotlInternal.utils.convertToString(baseKey)]; var preferredSession = record._sessions[toString(baseKey)];
if (preferredSession !== undefined) if (preferredSession !== undefined)
return preferredSession; return preferredSession;
@ -34397,7 +34386,7 @@ window.axolotl.protocol = function(storage_interface) {
if (salt.byteLength != 32) if (salt.byteLength != 32)
throw new Error("Got salt of incorrect length"); throw new Error("Got salt of incorrect length");
info = axolotlInternal.utils.convertToArrayBuffer(info); // TODO: maybe convert calls? info = toArrayBuffer(info); // TODO: maybe convert calls?
return axolotlInternal.crypto.HKDF(input, salt, info); return axolotlInternal.crypto.HKDF(input, salt, info);
} }
@ -34425,12 +34414,19 @@ window.axolotl.protocol = function(storage_interface) {
var calculateRatchet = function(session, remoteKey, sending) { var calculateRatchet = function(session, remoteKey, sending) {
var ratchet = session.currentRatchet; var ratchet = session.currentRatchet;
return axolotlInternal.crypto.ECDHE(remoteKey, axolotlInternal.utils.convertToArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) { return axolotlInternal.crypto.ECDHE(remoteKey, toArrayBuffer(ratchet.ephemeralKeyPair.privKey)).then(function(sharedSecret) {
return HKDF(sharedSecret, axolotlInternal.utils.convertToArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) { return HKDF(sharedSecret, toArrayBuffer(ratchet.rootKey), "WhisperRatchet").then(function(masterKey) {
if (sending) var ephemeralPublicKey;
session[axolotlInternal.utils.convertToString(ratchet.ephemeralKeyPair.pubKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } }; if (sending) {
else ephemeralPublicKey = ratchet.ephemeralKeyPair.pubKey;
session[axolotlInternal.utils.convertToString(remoteKey)] = { messageKeys: {}, chainKey: { counter: -1, key: masterKey[1] } }; }
else {
ephemeralPublicKey = remoteKey;
}
session[toString(ephemeralPublicKey)] = {
messageKeys: {},
chainKey: { counter: -1, key: masterKey[1] }
};
ratchet.rootKey = masterKey[0]; ratchet.rootKey = masterKey[0];
}); });
}); });
@ -34520,7 +34516,7 @@ window.axolotl.protocol = function(storage_interface) {
var newList = []; var newList = [];
for (var i = 0; i < session.oldRatchetList.length; i++) { for (var i = 0; i < session.oldRatchetList.length; i++) {
var entry = session.oldRatchetList[i]; var entry = session.oldRatchetList[i];
var ratchet = axolotlInternal.utils.convertToString(entry.ephemeralKey); var ratchet = toString(entry.ephemeralKey);
console.log("Checking old chain with added time " + (entry.added/1000)); console.log("Checking old chain with added time " + (entry.added/1000));
if ((!objectContainsKeys(session[ratchet].messageKeys) && (session[ratchet].chainKey === undefined || session[ratchet].chainKey.key === undefined)) if ((!objectContainsKeys(session[ratchet].messageKeys) && (session[ratchet].chainKey === undefined || session[ratchet].chainKey.key === undefined))
|| entry.added < Date.now() - MESSAGE_LOST_THRESHOLD_MS) { || entry.added < Date.now() - MESSAGE_LOST_THRESHOLD_MS) {
@ -34541,7 +34537,7 @@ window.axolotl.protocol = function(storage_interface) {
// but we cannot send messages or step the ratchet // but we cannot send messages or step the ratchet
// Delete current sending ratchet // Delete current sending ratchet
delete session[axolotlInternal.utils.convertToString(session.currentRatchet.ephemeralKeyPair.pubKey)]; delete session[toString(session.currentRatchet.ephemeralKeyPair.pubKey)];
// Move all receive ratchets to the oldRatchetList to mark them for deletion // Move all receive ratchets to the oldRatchetList to mark them for deletion
for (var i in session) { for (var i in session) {
if (session[i].chainKey !== undefined && session[i].chainKey.key !== undefined) { if (session[i].chainKey !== undefined && session[i].chainKey.key !== undefined) {
@ -34571,7 +34567,7 @@ window.axolotl.protocol = function(storage_interface) {
var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) { var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) {
return storage_interface.getPreKey(message.preKeyId).then(function(preKeyPair) { return storage_interface.getPreKey(message.preKeyId).then(function(preKeyPair) {
return storage_interface.getSignedPreKey(message.signedPreKeyId).then(function(signedPreKeyPair) { return storage_interface.getSignedPreKey(message.signedPreKeyId).then(function(signedPreKeyPair) {
return crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.baseKey)).then(function(session) { return crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, toArrayBuffer(message.baseKey)).then(function(session) {
return crypto_storage.getOpenSession(encodedNumber).then(function(open_session) { return crypto_storage.getOpenSession(encodedNumber).then(function(open_session) {
if (signedPreKeyPair === undefined) { if (signedPreKeyPair === undefined) {
// Session may or may not be the right one, but if its not, we can't do anything about it // Session may or may not be the right one, but if its not, we can't do anything about it
@ -34583,13 +34579,13 @@ window.axolotl.protocol = function(storage_interface) {
} }
if (session !== undefined) { if (session !== undefined) {
// Duplicate PreKeyMessage for session: // Duplicate PreKeyMessage for session:
if (axolotlInternal.utils.isEqual(session.indexInfo.baseKey, message.baseKey)) if (util.isEqual(session.indexInfo.baseKey, message.baseKey))
return Promise.resolve([session, undefined]); return Promise.resolve([session, undefined]);
// We already had a session/known identity key: // We already had a session/known identity key:
if (axolotlInternal.utils.isEqual(session.indexInfo.remoteIdentityKey, message.identityKey)) { if (util.isEqual(session.indexInfo.remoteIdentityKey, message.identityKey)) {
// If the identity key matches the previous one, close the previous one and use the new one // If the identity key matches the previous one, close the previous one and use the new one
if (open_session !== undefined) if (open_session !== undefin)
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
@ -34598,7 +34594,7 @@ window.axolotl.protocol = function(storage_interface) {
throw e; throw e;
} }
} }
return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.identityKey), axolotlInternal.utils.convertToArrayBuffer(message.baseKey), undefined) return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, toArrayBuffer(message.identityKey), toArrayBuffer(message.baseKey), undefined)
.then(function(new_session) { .then(function(new_session) {
// Note that the session is not actually saved until the very end of decryptWhisperMessage // Note that the session is not actually saved until the very end of decryptWhisperMessage
// ... to ensure that the sender actually holds the private keys for all reported pubkeys // ... to ensure that the sender actually holds the private keys for all reported pubkeys
@ -34627,7 +34623,7 @@ window.axolotl.protocol = function(storage_interface) {
if (chain.chainKey.key === undefined) if (chain.chainKey.key === undefined)
throw new Error("Got invalid request to extend chain after it was already closed"); throw new Error("Got invalid request to extend chain after it was already closed");
var key = axolotlInternal.utils.convertToArrayBuffer(chain.chainKey.key); var key = toArrayBuffer(chain.chainKey.key);
var byteArray = new Uint8Array(1); var byteArray = new Uint8Array(1);
byteArray[0] = 1; byteArray[0] = 1;
return axolotlInternal.crypto.sign(key, byteArray.buffer).then(function(mac) { return axolotlInternal.crypto.sign(key, byteArray.buffer).then(function(mac) {
@ -34642,7 +34638,7 @@ window.axolotl.protocol = function(storage_interface) {
} }
var maybeStepRatchet = function(session, remoteKey, previousCounter) { var maybeStepRatchet = function(session, remoteKey, previousCounter) {
if (session[axolotlInternal.utils.convertToString(remoteKey)] !== undefined) if (session[toString(remoteKey)] !== undefined)
return Promise.resolve(); return Promise.resolve();
var ratchet = session.currentRatchet; var ratchet = session.currentRatchet;
@ -34650,7 +34646,7 @@ window.axolotl.protocol = function(storage_interface) {
var finish = function() { var finish = function() {
return calculateRatchet(session, remoteKey, false).then(function() { return calculateRatchet(session, remoteKey, false).then(function() {
// Now swap the ephemeral key and calculate the new sending chain // Now swap the ephemeral key and calculate the new sending chain
var previousRatchet = axolotlInternal.utils.convertToString(ratchet.ephemeralKeyPair.pubKey); var previousRatchet = toString(ratchet.ephemeralKeyPair.pubKey);
if (session[previousRatchet] !== undefined) { if (session[previousRatchet] !== undefined) {
ratchet.previousCounter = session[previousRatchet].chainKey.counter; ratchet.previousCounter = session[previousRatchet].chainKey.counter;
delete session[previousRatchet]; delete session[previousRatchet];
@ -34665,12 +34661,12 @@ window.axolotl.protocol = function(storage_interface) {
}); });
} }
var previousRatchet = session[axolotlInternal.utils.convertToString(ratchet.lastRemoteEphemeralKey)]; var previousRatchet = session[toString(ratchet.lastRemoteEphemeralKey)];
if (previousRatchet !== undefined) { if (previousRatchet !== undefined) {
return fillMessageKeys(previousRatchet, previousCounter).then(function() { return fillMessageKeys(previousRatchet, previousCounter).then(function() {
delete previousRatchet.chainKey.key; delete previousRatchet.chainKey.key;
if (!objectContainsKeys(previousRatchet.messageKeys)) if (!objectContainsKeys(previousRatchet.messageKeys))
delete session[axolotlInternal.utils.convertToString(ratchet.lastRemoteEphemeralKey)]; delete session[toString(ratchet.lastRemoteEphemeralKey)];
else else
session.oldRatchetList[session.oldRatchetList.length] = { added: Date.now(), ephemeralKey: ratchet.lastRemoteEphemeralKey }; session.oldRatchetList[session.oldRatchetList.length] = { added: Date.now(), ephemeralKey: ratchet.lastRemoteEphemeralKey };
}).then(finish); }).then(finish);
@ -34679,16 +34675,18 @@ window.axolotl.protocol = function(storage_interface) {
} }
var doDecryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) { var doDecryptWhisperMessage = function(encodedNumber, messageBytes, session, registrationId) {
if (messageBytes[0] != String.fromCharCode((3 << 4) | 3)) if (!messageBytes instanceof ArrayBuffer) {
throw new Error("Expected messageBytes to be an ArrayBuffer");
}
var version = (new Uint8Array(messageBytes))[0];
if (version !== ((3 << 4) | 3)) {
throw new Error("Bad version number on WhisperMessage"); throw new Error("Bad version number on WhisperMessage");
}
var messageProto = messageBytes.slice(1, messageBytes.byteLength- 8);
var mac = messageBytes.slice(messageBytes.byteLength - 8, messageBytes.byteLength);
var messageProto = messageBytes.substring(1, messageBytes.length - 8); var message = axolotlInternal.protobuf.WhisperMessage.decode(messageProto);
var mac = axolotlInternal.utils.convertToArrayBuffer( var remoteEphemeralKey = message.ephemeralKey.toArrayBuffer();
messageBytes.substring(messageBytes.length - 8, messageBytes.length)
);
var message = axolotlInternal.protobuf.WhisperMessage.decode(messageProto, 'binary');
var remoteEphemeralKey = axolotlInternal.utils.convertToArrayBuffer(message.ephemeralKey);
var promise; var promise;
if (session === undefined) { if (session === undefined) {
@ -34703,7 +34701,7 @@ window.axolotl.protocol = function(storage_interface) {
return promise.then(function(session) { return promise.then(function(session) {
return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() { return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() {
var chain = session[axolotlInternal.utils.convertToString(message.ephemeralKey)]; var chain = session[toString(message.ephemeralKey)];
return fillMessageKeys(chain, message.counter).then(function() { return fillMessageKeys(chain, message.counter).then(function() {
var messageKey = chain.messageKeys[message.counter]; var messageKey = chain.messageKeys[message.counter];
@ -34712,21 +34710,22 @@ window.axolotl.protocol = function(storage_interface) {
e.name = 'MessageCounterError'; e.name = 'MessageCounterError';
throw e; throw e;
} }
return HKDF(axolotlInternal.utils.convertToArrayBuffer(messageKey), '', "WhisperMessageKeys").then(function(keys) { return HKDF(toArrayBuffer(messageKey), '', "WhisperMessageKeys").then(function(keys) {
return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) { return storage_interface.getMyIdentityKey().then(function(ourIdentityKey) {
delete chain.messageKeys[message.counter]; delete chain.messageKeys[message.counter];
var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto); var macInput = new Uint8Array(messageProto.byteLength + 33*2 + 1);
var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1); macInput.set(new Uint8Array(toArrayBuffer(session.indexInfo.remoteIdentityKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey))); macInput.set(new Uint8Array(toArrayBuffer(ourIdentityKey.pubKey)), 33);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1); macInput.set(new Uint8Array(messageProto), 33*2 + 1);
return verifyMAC(macInput.buffer, keys[1], mac, 8).then(function() { return verifyMAC(macInput.buffer, keys[1], mac, 8).then(function() {
return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16)) return axolotlInternal.crypto.decrypt(
.then(function(paddedPlaintext) { keys[0],
message.ciphertext.toArrayBuffer(),
keys[2].slice(0, 16)
).then(function(paddedPlaintext) {
paddedPlaintext = new Uint8Array(paddedPlaintext); paddedPlaintext = new Uint8Array(paddedPlaintext);
var plaintext; var plaintext;
for (var i = paddedPlaintext.length - 1; i >= 0; i--) { for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
@ -34763,15 +34762,20 @@ window.axolotl.protocol = function(storage_interface) {
//TODO: SHARP EDGE HERE //TODO: SHARP EDGE HERE
//XXX: Also, you MUST call the session close function before processing another message....except its a promise...so you literally cant! //XXX: Also, you MUST call the session close function before processing another message....except its a promise...so you literally cant!
// returns decrypted plaintext and a function that must be called if the message indicates session close // returns decrypted plaintext and a function that must be called if the message indicates session close
self.decryptWhisperMessage = function(encodedNumber, messageBytes, session) { self.decryptWhisperMessage = function(encodedNumber, messageBytes) {
return doDecryptWhisperMessage(encodedNumber, messageBytes, session); return doDecryptWhisperMessage(encodedNumber, toArrayBuffer(messageBytes));
} };
// Inits a session (maybe) and then decrypts the message // Inits a session (maybe) and then decrypts the message
self.handlePreKeyWhisperMessage = function(from, encodedMessage, encoding) { self.handlePreKeyWhisperMessage = function(from, encodedMessage, encoding) {
var preKeyProto = axolotlInternal.protobuf.PreKeyWhisperMessage.decode(encodedMessage, encoding); var preKeyProto = axolotlInternal.protobuf.PreKeyWhisperMessage.decode(encodedMessage, encoding);
return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) { return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) {
return doDecryptWhisperMessage(from, axolotlInternal.utils.convertToString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) { return doDecryptWhisperMessage(
from,
preKeyProto.message.toArrayBuffer(),
sessions[0],
preKeyProto.registrationId
).then(function(result) {
if (sessions[1] !== undefined) if (sessions[1] !== undefined)
return sessions[1]().then(function() { return result; }); return sessions[1]().then(function() { return result; });
return result; return result;
@ -34807,22 +34811,22 @@ window.axolotl.protocol = function(storage_interface) {
paddedPlaintext.set(new Uint8Array(plaintext)); paddedPlaintext.set(new Uint8Array(plaintext));
paddedPlaintext[plaintext.byteLength] = 0x80; paddedPlaintext[plaintext.byteLength] = 0x80;
msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey); msg.ephemeralKey = toArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)]; var chain = session[toString(msg.ephemeralKey)];
return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() { return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) { return HKDF(toArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
delete chain.messageKeys[chain.chainKey.counter]; delete chain.messageKeys[chain.chainKey.counter];
msg.counter = chain.chainKey.counter; msg.counter = chain.chainKey.counter;
msg.previousCounter = session.currentRatchet.previousCounter; msg.previousCounter = session.currentRatchet.previousCounter;
return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) { return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
msg.ciphertext = ciphertext; msg.ciphertext = ciphertext;
var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode()); var encodedMsg = toArrayBuffer(msg.encode());
var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1); var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey))); macInput.set(new Uint8Array(toArrayBuffer(ourIdentityKey.pubKey)));
macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33); macInput.set(new Uint8Array(toArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
macInput[33*2] = (3 << 4) | 3; macInput[33*2] = (3 << 4) | 3;
macInput.set(new Uint8Array(encodedMsg), 33*2 + 1); macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
@ -34844,26 +34848,26 @@ window.axolotl.protocol = function(storage_interface) {
} }
var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage(); var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage();
preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(ourIdentityKey.pubKey); preKeyMsg.identityKey = toArrayBuffer(ourIdentityKey.pubKey);
preKeyMsg.registrationId = myRegistrationId; preKeyMsg.registrationId = myRegistrationId;
if (session === undefined) { if (session === undefined) {
var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey); var deviceIdentityKey = toArrayBuffer(deviceObject.identityKey);
var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey); var deviceSignedKey = toArrayBuffer(deviceObject.signedKey);
return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() { return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, toArrayBuffer(deviceObject.signedKeySignature)).then(function() {
return axolotlInternal.crypto.createKeyPair().then(function(baseKey) { return axolotlInternal.crypto.createKeyPair().then(function(baseKey) {
preKeyMsg.preKeyId = deviceObject.preKeyId; preKeyMsg.preKeyId = deviceObject.preKeyId;
preKeyMsg.signedPreKeyId = deviceObject.signedKeyId; preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey); preKeyMsg.baseKey = toArrayBuffer(baseKey.pubKey);
return initSession(true, baseKey, undefined, return initSession(true, baseKey, undefined,
deviceObject.encodedNumber, deviceIdentityKey, deviceObject.encodedNumber, deviceIdentityKey,
axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey), toArrayBuffer(deviceObject.preKey),
deviceSignedKey).then(function(new_session) { deviceSignedKey).then(function(new_session) {
session = new_session; session = new_session;
session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey }; session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
return doEncryptPushMessageContent().then(function(message) { return doEncryptPushMessageContent().then(function(message) {
preKeyMsg.message = message; preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode()); var result = String.fromCharCode((3 << 4) | 3) + toString(preKeyMsg.encode());
return {type: 3, body: result}; return {type: 3, body: result};
}); });
}); });
@ -34872,15 +34876,15 @@ window.axolotl.protocol = function(storage_interface) {
} else } else
return doEncryptPushMessageContent().then(function(message) { return doEncryptPushMessageContent().then(function(message) {
if (session.pendingPreKey !== undefined) { if (session.pendingPreKey !== undefined) {
preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey); preKeyMsg.baseKey = toArrayBuffer(session.pendingPreKey.baseKey);
preKeyMsg.preKeyId = session.pendingPreKey.preKeyId; preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId; preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
preKeyMsg.message = message; preKeyMsg.message = message;
var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode()); var result = String.fromCharCode((3 << 4) | 3) + toString(preKeyMsg.encode());
return {type: 3, body: result}; return {type: 3, body: result};
} else } else
return {type: 1, body: axolotlInternal.utils.convertToString(message)}; return {type: 1, body: toString(message)};
}); });
}); });
}); });
@ -34892,8 +34896,8 @@ window.axolotl.protocol = function(storage_interface) {
var keyPair; var keyPair;
socketInfo.decryptAndHandleDeviceInit = function(deviceInit) { socketInfo.decryptAndHandleDeviceInit = function(deviceInit) {
var masterEphemeral = axolotlInternal.utils.convertToArrayBuffer(deviceInit.publicKey); var masterEphemeral = toArrayBuffer(deviceInit.publicKey);
var message = axolotlInternal.utils.convertToArrayBuffer(deviceInit.body); var message = toArrayBuffer(deviceInit.body);
return axolotlInternal.crypto.ECDHE(masterEphemeral, keyPair.privKey).then(function(ecRes) { return axolotlInternal.crypto.ECDHE(masterEphemeral, keyPair.privKey).then(function(ecRes) {
return HKDF(ecRes, '', "TextSecure Provisioning Message").then(function(keys) { return HKDF(ecRes, '', "TextSecure Provisioning Message").then(function(keys) {
@ -35084,19 +35088,51 @@ var axolotlInternal = axolotlInternal || {};
axolotlInternal.RecipientRecord = function() { axolotlInternal.RecipientRecord = function() {
'use strict'; 'use strict';
function ensureStringed(thing) {
if (typeof thing == "string" || typeof thing == "number" || typeof thing == "boolean")
return thing;
else if (util.isStringable(thing))
return util.stringObject(thing);
else if (thing instanceof Array) {
var res = [];
for (var i = 0; i < thing.length; i++)
res[i] = ensureStringed(thing[i]);
return res;
} else if (thing === Object(thing)) {
var res = {};
for (var key in thing)
res[key] = ensureStringed(thing[key]);
return res;
} else if (thing === null)
return null;
else
throw new Error("unsure of how to jsonify object of type " + typeof thing);
}
function jsonThing(thing) {
return JSON.stringify(ensureStringed(thing)); //TODO: jquery???
}
var RecipientRecord = function(identityKey, registrationId) { var RecipientRecord = function(identityKey, registrationId) {
this._sessions = {}; this._sessions = {};
this.identityKey = identityKey !== undefined ? axolotlInternal.utils.convertToString(identityKey) : null; if (typeof identityKey !== 'string') {
throw new Error('RecipientRecord: Invalid identityKey');
}
this.identityKey = identityKey;
this.registrationId = registrationId; this.registrationId = registrationId;
if (this.registrationId === undefined || typeof this.registrationId !== "number") if (this.registrationId === undefined || typeof this.registrationId !== 'number') {
this.registrationId = null; this.registrationId = null;
}
}; };
RecipientRecord.prototype.serialize = function() { RecipientRecord.prototype.serialize = function() {
return axolotlInternal.utils.jsonThing({sessions: this._sessions, registrationId: this.registrationId, identityKey: this.identityKey}); return jsonThing({
} sessions : this._sessions,
registrationId : this.registrationId,
identityKey : this.identityKey
});
};
RecipientRecord.deserialize = function(serialized) { RecipientRecord.deserialize = function(serialized) {
var data = JSON.parse(serialized); var data = JSON.parse(serialized);
@ -35107,11 +35143,11 @@ axolotlInternal.RecipientRecord = function() {
if (record.identityKey === undefined || record.registrationId === undefined) if (record.identityKey === undefined || record.registrationId === undefined)
throw new Error("Error deserializing RecipientRecord"); throw new Error("Error deserializing RecipientRecord");
return record; return record;
} };
RecipientRecord.prototype.haveOpenSession = function() { RecipientRecord.prototype.haveOpenSession = function() {
return this.registrationId !== null; return this.registrationId !== null;
} };
return RecipientRecord; return RecipientRecord;
}(); }();