Integrate libaxolotl async storage changes
* Session records are now opaque strings, so treat them that way: - no more cross checking identity key and session records - Move hasOpenSession to axolotl wrapper - Remote registration ids must be fetched async'ly via protocol wrapper * Implement async AxolotlStore using textsecure.storage * Add some db stores and move prekeys and signed keys to indexeddb * Add storage tests * Rename identityKey storage key from libaxolotl25519KeyidentityKey to simply identityKey, since it's no longer hardcoded in libaxolotl * Rework registration and key-generation, keeping logic in libtextsecure and rendering in options.js. * Remove key_worker since workers are handled at the libaxolotl level now
This commit is contained in:
parent
8304aa903a
commit
96eafc7750
20 changed files with 1014 additions and 40445 deletions
16
Gruntfile.js
16
Gruntfile.js
|
@ -61,16 +61,6 @@ module.exports = function(grunt) {
|
||||||
],
|
],
|
||||||
dest: 'js/libtextsecure.js',
|
dest: 'js/libtextsecure.js',
|
||||||
},
|
},
|
||||||
key_worker: {
|
|
||||||
options: {
|
|
||||||
banner: 'var window = this;\n',
|
|
||||||
},
|
|
||||||
src: [
|
|
||||||
'js/libtextsecure.js',
|
|
||||||
'libtextsecure/key_worker.js'
|
|
||||||
],
|
|
||||||
dest: 'js/key_worker.js'
|
|
||||||
},
|
|
||||||
libtextsecuretest: {
|
libtextsecuretest: {
|
||||||
src: [
|
src: [
|
||||||
'components/mock-socket/dist/mock-socket.js',
|
'components/mock-socket/dist/mock-socket.js',
|
||||||
|
@ -148,7 +138,7 @@ module.exports = function(grunt) {
|
||||||
},
|
},
|
||||||
jscs: {
|
jscs: {
|
||||||
all: {
|
all: {
|
||||||
src: ['js/**/*.js', '!js/libtextsecure.js', '!js/key_worker.js', '!js/components.js', 'test/**/*.js']
|
src: ['js/**/*.js', '!js/libtextsecure.js', '!js/components.js', 'test/**/*.js']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -164,10 +154,6 @@ module.exports = function(grunt) {
|
||||||
files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
|
files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
|
||||||
tasks: ['concat:libtextsecure']
|
tasks: ['concat:libtextsecure']
|
||||||
},
|
},
|
||||||
key_worker: {
|
|
||||||
files: ['<%= concat.key_worker.src %>'],
|
|
||||||
tasks: ['concat:key_worker']
|
|
||||||
},
|
|
||||||
dist: {
|
dist: {
|
||||||
files: ['<%= dist.src %>'],
|
files: ['<%= dist.src %>'],
|
||||||
tasks: ['copy']
|
tasks: ['copy']
|
||||||
|
|
|
@ -230,6 +230,7 @@
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<script type="text/javascript" src="js/components.js"></script>
|
||||||
<script type="text/javascript" src="js/database.js"></script>
|
<script type="text/javascript" src="js/database.js"></script>
|
||||||
|
<script type="text/javascript" src="js/axolotl_store.js"></script>
|
||||||
<script type="text/javascript" src="js/libtextsecure.js"></script>
|
<script type="text/javascript" src="js/libtextsecure.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/notifications.js"></script>
|
<script type="text/javascript" src="js/notifications.js"></script>
|
||||||
|
|
183
js/axolotl_store.js
Normal file
183
js/axolotl_store.js
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/* vim: ts=4:sw=4
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
;(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function isStringable(thing) {
|
||||||
|
return (thing === Object(thing) &&
|
||||||
|
(thing.__proto__ == StaticArrayBufferProto ||
|
||||||
|
thing.__proto__ == StaticUint8ArrayProto ||
|
||||||
|
thing.__proto__ == StaticByteBufferProto));
|
||||||
|
}
|
||||||
|
function convertToArrayBuffer(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Model = Backbone.Model.extend({ database: Whisper.Database });
|
||||||
|
var PreKey = Model.extend({ storeName: 'preKeys' });
|
||||||
|
var SignedPreKey = Model.extend({ storeName: 'signedPreKeys' });
|
||||||
|
|
||||||
|
function AxolotlStore() {}
|
||||||
|
|
||||||
|
AxolotlStore.prototype = {
|
||||||
|
constructor: AxolotlStore,
|
||||||
|
get: function(key,defaultValue) {
|
||||||
|
return textsecure.storage.get(key, defaultValue);
|
||||||
|
},
|
||||||
|
put: function(key, value) {
|
||||||
|
textsecure.storage.put(key, value);
|
||||||
|
},
|
||||||
|
remove: function(key) {
|
||||||
|
textsecure.storage.remove(key);
|
||||||
|
},
|
||||||
|
getMyIdentityKey: function() {
|
||||||
|
var res = textsecure.storage.get('identityKey');
|
||||||
|
if (res === undefined)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
pubKey: convertToArrayBuffer(res.pubKey),
|
||||||
|
privKey: convertToArrayBuffer(res.privKey)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getMyRegistrationId: function() {
|
||||||
|
return textsecure.storage.get('registrationId');
|
||||||
|
},
|
||||||
|
|
||||||
|
getIdentityKey: function(identifier) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to get identity key for undefined/null key");
|
||||||
|
return Promise.resolve(convertToArrayBuffer(textsecure.storage.devices.getIdentityKeyForNumber(textsecure.utils.unencodeNumber(identifier)[0])));
|
||||||
|
},
|
||||||
|
putIdentityKey: function(identifier, identityKey) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to put identity key for undefined/null key");
|
||||||
|
return Promise.resolve(textsecure.storage.devices.checkSaveIdentityKeyForNumber(textsecure.utils.unencodeNumber(identifier)[0], identityKey));
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Returns a prekeypair object or undefined */
|
||||||
|
getPreKey: function(keyId) {
|
||||||
|
var prekey = new PreKey({id: keyId});
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
prekey.fetch().then(function() {
|
||||||
|
resolve({
|
||||||
|
pubKey: prekey.attributes.publicKey,
|
||||||
|
privKey: prekey.attributes.privateKey
|
||||||
|
});
|
||||||
|
}).fail(resolve);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
putPreKey: function(keyId, keyPair) {
|
||||||
|
var prekey = new PreKey({
|
||||||
|
id : keyId,
|
||||||
|
publicKey : keyPair.pubKey,
|
||||||
|
privateKey : keyPair.privKey
|
||||||
|
});
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
prekey.save().always(function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removePreKey: function(keyId) {
|
||||||
|
var prekey = new PreKey({id: keyId});
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
prekey.destroy().then(function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Returns a signed keypair object or undefined */
|
||||||
|
getSignedPreKey: function(keyId) {
|
||||||
|
var prekey = new SignedPreKey({id: keyId});
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
prekey.fetch().then(function() {
|
||||||
|
resolve({
|
||||||
|
pubKey: prekey.attributes.publicKey,
|
||||||
|
privKey: prekey.attributes.privateKey
|
||||||
|
});
|
||||||
|
}).fail(resolve);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
putSignedPreKey: function(keyId, keyPair) {
|
||||||
|
var prekey = new SignedPreKey({
|
||||||
|
id : keyId,
|
||||||
|
publicKey : keyPair.pubKey,
|
||||||
|
privateKey : keyPair.privKey
|
||||||
|
});
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
prekey.save().always(function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeSignedPreKey: function(keyId) {
|
||||||
|
var prekey = new SignedPreKey({id: keyId});
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
prekey.destroy().then(function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getSession: function(identifier) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to get session for undefined/null key");
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(textsecure.storage.sessions.getSessionsForNumber(identifier));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
putSession: function(identifier, record) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to put session for undefined/null key");
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(textsecure.storage.sessions.putSessionsForDevice(identifier, record));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.AxolotlStore = AxolotlStore;
|
||||||
|
})();
|
|
@ -33,6 +33,10 @@
|
||||||
conversations.createIndex("inbox", "active_at", { unique: false });
|
conversations.createIndex("inbox", "active_at", { unique: false });
|
||||||
conversations.createIndex("group", "members", { unique: false, multiEntry: true });
|
conversations.createIndex("group", "members", { unique: false, multiEntry: true });
|
||||||
conversations.createIndex("type", "type", { unique: false });
|
conversations.createIndex("type", "type", { unique: false });
|
||||||
|
|
||||||
|
var preKeys = transaction.db.createObjectStore("preKeys");
|
||||||
|
var signedPreKeys = transaction.db.createObjectStore("signedPreKeys");
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40049
js/key_worker.js
40049
js/key_worker.js
File diff suppressed because one or more lines are too long
|
@ -37771,47 +37771,12 @@ axolotlInternal.RecipientRecord = function() {
|
||||||
}();
|
}();
|
||||||
|
|
||||||
})();
|
})();
|
||||||
'use strict';
|
|
||||||
|
|
||||||
;(function() {
|
;(function() {
|
||||||
var axolotlInstance = axolotl.protocol({
|
'use strict';
|
||||||
getMyRegistrationId: function() {
|
window.textsecure = window.textsecure || {};
|
||||||
return textsecure.storage.get("registrationId");
|
window.textsecure.storage = window.textsecure.storage || {};
|
||||||
},
|
textsecure.storage.axolotl = new AxolotlStore();
|
||||||
put: function(key, value) {
|
var axolotlInstance = axolotl.protocol(textsecure.storage.axolotl);
|
||||||
return textsecure.storage.put("libaxolotl" + key, value);
|
|
||||||
},
|
|
||||||
get: function(key, defaultValue) {
|
|
||||||
return textsecure.storage.get("libaxolotl" + key, defaultValue);
|
|
||||||
},
|
|
||||||
remove: function(key) {
|
|
||||||
return textsecure.storage.remove("libaxolotl" + key);
|
|
||||||
},
|
|
||||||
|
|
||||||
identityKeys: {
|
|
||||||
get: function(identifier) {
|
|
||||||
return textsecure.storage.devices.getIdentityKeyForNumber(textsecure.utils.unencodeNumber(identifier)[0]);
|
|
||||||
},
|
|
||||||
put: function(identifier, identityKey) {
|
|
||||||
return textsecure.storage.devices.checkSaveIdentityKeyForNumber(textsecure.utils.unencodeNumber(identifier)[0], identityKey);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
sessions: {
|
|
||||||
get: function(identifier) {
|
|
||||||
return textsecure.storage.sessions.getSessionsForNumber(identifier);
|
|
||||||
},
|
|
||||||
put: function(identifier, record) {
|
|
||||||
return textsecure.storage.sessions.putSessionsForDevice(identifier, record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(keys) {
|
|
||||||
return textsecure.api.registerKeys(keys).catch(function(e) {
|
|
||||||
//TODO: Notify the user somehow?
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var decodeMessageContents = function(res) {
|
var decodeMessageContents = function(res) {
|
||||||
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
|
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
|
||||||
|
@ -37826,7 +37791,7 @@ axolotlInternal.RecipientRecord = function() {
|
||||||
|
|
||||||
var handlePreKeyWhisperMessage = function(from, message) {
|
var handlePreKeyWhisperMessage = function(from, message) {
|
||||||
try {
|
try {
|
||||||
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, message);
|
return axolotlInstance.handlePreKeyWhisperMessage(from, message);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if (e.message === 'Unknown identity key') {
|
if (e.message === 'Unknown identity key') {
|
||||||
// create an error that the UI will pick up and ask the
|
// create an error that the UI will pick up and ask the
|
||||||
|
@ -37845,7 +37810,7 @@ axolotlInternal.RecipientRecord = function() {
|
||||||
return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message));
|
return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message));
|
||||||
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
|
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
|
||||||
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
||||||
return textsecure.protocol_wrapper.decryptWhisperMessage(from, getString(proto.message)).then(decodeMessageContents);
|
return axolotlInstance.decryptWhisperMessage(from, getString(proto.message)).then(decodeMessageContents);
|
||||||
case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE:
|
case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE:
|
||||||
if (proto.message.readUint8() != ((3 << 4) | 3))
|
if (proto.message.readUint8() != ((3 << 4) | 3))
|
||||||
throw new Error("Bad version byte");
|
throw new Error("Bad version byte");
|
||||||
|
@ -37860,27 +37825,34 @@ axolotlInternal.RecipientRecord = function() {
|
||||||
closeOpenSessionForDevice: function(encodedNumber) {
|
closeOpenSessionForDevice: function(encodedNumber) {
|
||||||
return axolotlInstance.closeOpenSessionForDevice(encodedNumber)
|
return axolotlInstance.closeOpenSessionForDevice(encodedNumber)
|
||||||
},
|
},
|
||||||
decryptWhisperMessage: function(encodedNumber, messageBytes, session) {
|
|
||||||
return axolotlInstance.decryptWhisperMessage(encodedNumber, messageBytes, session);
|
|
||||||
},
|
|
||||||
handlePreKeyWhisperMessage: function(from, encodedMessage) {
|
|
||||||
return axolotlInstance.handlePreKeyWhisperMessage(from, encodedMessage);
|
|
||||||
},
|
|
||||||
encryptMessageFor: function(deviceObject, pushMessageContent) {
|
encryptMessageFor: function(deviceObject, pushMessageContent) {
|
||||||
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
|
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
|
||||||
},
|
},
|
||||||
generateKeys: function() {
|
generateKeys: function(count, progressCallback) {
|
||||||
return axolotlInstance.generateKeys();
|
if (textsecure.worker_path) {
|
||||||
|
axolotlInstance.startWorker(textsecure.worker_path);
|
||||||
|
}
|
||||||
|
return generateKeys(count, progressCallback).then(function(result) {
|
||||||
|
axolotlInstance.stopWorker();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
createIdentityKeyRecvSocket: function() {
|
createIdentityKeyRecvSocket: function() {
|
||||||
return axolotlInstance.createIdentityKeyRecvSocket();
|
return axolotlInstance.createIdentityKeyRecvSocket();
|
||||||
|
},
|
||||||
|
hasOpenSession: function(encodedNumber) {
|
||||||
|
return axolotlInstance.hasOpenSession(encodedNumber);
|
||||||
|
},
|
||||||
|
getRegistrationId: function(encodedNumber) {
|
||||||
|
return axolotlInstance.getRegistrationId(encodedNumber);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var tryMessageAgain = function(from, encodedMessage) {
|
var tryMessageAgain = function(from, encodedMessage) {
|
||||||
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, encodedMessage).then(decodeMessageContents);
|
return axolotlInstance.handlePreKeyWhisperMessage(from, encodedMessage).then(decodeMessageContents);
|
||||||
}
|
}
|
||||||
textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.INIT_SESSION);
|
textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.INIT_SESSION);
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/* vim: ts=4:sw=4:expandtab
|
/* vim: ts=4:sw=4:expandtab
|
||||||
|
@ -38136,46 +38108,30 @@ axolotlInternal.RecipientRecord = function() {
|
||||||
if (sessions[deviceId] === undefined)
|
if (sessions[deviceId] === undefined)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
var record = new axolotl.sessions.RecipientRecord();
|
return sessions[deviceId];
|
||||||
record.deserialize(sessions[deviceId]);
|
|
||||||
if (getString(textsecure.storage.devices.getIdentityKeyForNumber(number)) !== getString(record.identityKey))
|
|
||||||
throw new Error("Got mismatched identity key on device object load");
|
|
||||||
return record;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
putSessionsForDevice: function(encodedNumber, record) {
|
putSessionsForDevice: function(encodedNumber, record) {
|
||||||
var number = textsecure.utils.unencodeNumber(encodedNumber)[0];
|
var number = textsecure.utils.unencodeNumber(encodedNumber)[0];
|
||||||
var deviceId = textsecure.utils.unencodeNumber(encodedNumber)[1];
|
var deviceId = textsecure.utils.unencodeNumber(encodedNumber)[1];
|
||||||
|
|
||||||
textsecure.storage.devices.checkSaveIdentityKeyForNumber(number, record.identityKey);
|
|
||||||
|
|
||||||
var sessions = textsecure.storage.get("sessions" + number);
|
var sessions = textsecure.storage.get("sessions" + number);
|
||||||
if (sessions === undefined)
|
if (sessions === undefined)
|
||||||
sessions = {};
|
sessions = {};
|
||||||
sessions[deviceId] = record.serialize();
|
sessions[deviceId] = record;
|
||||||
textsecure.storage.put("sessions" + number, sessions);
|
textsecure.storage.put("sessions" + number, sessions);
|
||||||
|
|
||||||
var device = textsecure.storage.devices.getDeviceObject(encodedNumber);
|
var device = textsecure.storage.devices.getDeviceObject(encodedNumber);
|
||||||
if (device === undefined) {
|
if (device === undefined) {
|
||||||
|
var identityKey = textsecure.storage.devices.getIdentityKeyForNumber(number);
|
||||||
device = { encodedNumber: encodedNumber,
|
device = { encodedNumber: encodedNumber,
|
||||||
//TODO: Remove this duplication
|
//TODO: Remove this duplication
|
||||||
identityKey: record.identityKey
|
identityKey: identityKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (getString(device.identityKey) !== getString(record.identityKey)) {
|
|
||||||
console.error("Got device object with key inconsistent after checkSaveIdentityKeyForNumber returned!");
|
|
||||||
throw new Error("Tried to put session for device with changed identity key");
|
|
||||||
}
|
|
||||||
return textsecure.storage.devices.saveDeviceObject(device);
|
return textsecure.storage.devices.saveDeviceObject(device);
|
||||||
},
|
},
|
||||||
|
|
||||||
haveOpenSessionForDevice: function(encodedNumber) {
|
|
||||||
var sessions = textsecure.storage.sessions.getSessionsForNumber(encodedNumber);
|
|
||||||
if (sessions === undefined || !sessions.haveOpenSession())
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use textsecure.storage.devices.removeIdentityKeyForNumber (which calls this) instead
|
// Use textsecure.storage.devices.removeIdentityKeyForNumber (which calls this) instead
|
||||||
_removeIdentityKeyForNumber: function(number) {
|
_removeIdentityKeyForNumber: function(number) {
|
||||||
textsecure.storage.remove("sessions" + number);
|
textsecure.storage.remove("sessions" + number);
|
||||||
|
@ -38820,6 +38776,8 @@ window.textsecure.utils = function() {
|
||||||
for (var key in thing)
|
for (var key in thing)
|
||||||
res[key] = ensureStringed(thing[key]);
|
res[key] = ensureStringed(thing[key]);
|
||||||
return res;
|
return res;
|
||||||
|
} else if (thing === null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
throw new Error("unsure of how to jsonify object of type " + typeof thing);
|
throw new Error("unsure of how to jsonify object of type " + typeof thing);
|
||||||
|
|
||||||
|
@ -38953,9 +38911,11 @@ textsecure.processDecrypted = function(decrypted, source) {
|
||||||
return Promise.all(promises).then(function() {
|
return Promise.all(promises).then(function() {
|
||||||
return decrypted;
|
return decrypted;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
function createAccount(number, verificationCode, identityKeyPair, single_device) {
|
||||||
|
textsecure.storage.put('identityKey', identityKeyPair);
|
||||||
|
|
||||||
window.textsecure.registerSingleDevice = function(number, verificationCode, stepDone) {
|
|
||||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
textsecure.storage.put('signaling_key', signalingKey);
|
||||||
|
|
||||||
|
@ -38963,39 +38923,95 @@ window.textsecure.registerSingleDevice = function(number, verificationCode, step
|
||||||
password = password.substring(0, password.length - 2);
|
password = password.substring(0, password.length - 2);
|
||||||
textsecure.storage.put("password", password);
|
textsecure.storage.put("password", password);
|
||||||
|
|
||||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
var registrationId = axolotl.util.generateRegistrationId();
|
||||||
registrationId = registrationId & 0x3fff;
|
|
||||||
textsecure.storage.put("registrationId", registrationId);
|
textsecure.storage.put("registrationId", registrationId);
|
||||||
|
|
||||||
return textsecure.api.confirmCode(number, verificationCode, password, signalingKey, registrationId, true).then(function() {
|
return textsecure.api.confirmCode(
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, 1);
|
number, verificationCode, password, signalingKey, registrationId, single_device
|
||||||
|
).then(function(response) {
|
||||||
|
textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1);
|
||||||
textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number));
|
textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number));
|
||||||
stepDone(1);
|
|
||||||
|
|
||||||
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
|
return textsecure.protocol_wrapper.generateKeys().then(textsecure.registration.done);
|
||||||
stepDone(2);
|
|
||||||
return textsecure.api.registerKeys(keys).then(function() {
|
|
||||||
stepDone(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.textsecure.registerSecondDevice = function(provisionMessage) {
|
function generateKeys(count, progressCallback) {
|
||||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
if (count === undefined) {
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
throw TypeError('generateKeys: count is undefined');
|
||||||
|
}
|
||||||
|
if (typeof progressCallback !== 'function') {
|
||||||
|
progressCallback = undefined;
|
||||||
|
}
|
||||||
|
var store = textsecure.storage.axolotl;
|
||||||
|
var identityKey = store.getMyIdentityKey();
|
||||||
|
var result = { preKeys: [], identityKey: identityKey.pubKey };
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
var password = btoa(getString(textsecure.crypto.getRandomBytes(16)));
|
var startId = textsecure.storage.get('maxPreKeyId', 1);
|
||||||
password = password.substring(0, password.length - 2);
|
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||||
textsecure.storage.put("password", password);
|
|
||||||
|
|
||||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
for (var keyId = startId; keyId < startId+count; ++keyId) {
|
||||||
registrationId = registrationId & 0x3fff;
|
promises.push(
|
||||||
textsecure.storage.put("registrationId", registrationId);
|
axolotl.util.generatePreKey(keyId).then(function(res) {
|
||||||
|
store.putPreKey(res.keyId, res.keyPair);
|
||||||
|
result.preKeys.push({
|
||||||
|
keyId : res.keyId,
|
||||||
|
publicKey : res.keyPair.pubKey
|
||||||
|
});
|
||||||
|
if (progressCallback) { progressCallback(); }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
promises.push(
|
||||||
|
axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) {
|
||||||
|
store.putSignedPreKey(res.keyId, res.keyPair);
|
||||||
|
result.signedPreKey = {
|
||||||
|
keyId : res.keyId,
|
||||||
|
publicKey : res.keyPair.pubKey,
|
||||||
|
signature : res.signature
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return textsecure.api.confirmCode(provisionMessage.number, provisionMessage.provisioningCode, password, signalingKey, registrationId, false).then(function(result) {
|
store.removeSignedPreKey(signedKeyId - 2);
|
||||||
textsecure.storage.user.setNumberAndDeviceId(provisionMessage.number, result.deviceId);
|
textsecure.storage.put('maxPreKeyId', startId + count);
|
||||||
textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(provisionMessage.number));
|
textsecure.storage.put('signedKeyId', signedKeyId + 1);
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function() {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.textsecure.registerSecondDevice = function(setProvisioningUrl, confirmNumber, progressCallback) {
|
||||||
|
return textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
new WebSocketResource(textsecure.api.getTempWebsocket(), function(request) {
|
||||||
|
if (request.path == "/v1/address" && request.verb == "PUT") {
|
||||||
|
var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body);
|
||||||
|
setProvisioningUrl([
|
||||||
|
'tsdevice:/?uuid=', proto.uuid, '&pub_key=',
|
||||||
|
encodeURIComponent(btoa(getString(cryptoInfo.pubKey)))
|
||||||
|
].join(''));
|
||||||
|
request.respond(200, 'OK');
|
||||||
|
} else if (request.path == "/v1/message" && request.verb == "PUT") {
|
||||||
|
var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary');
|
||||||
|
request.respond(200, 'OK');
|
||||||
|
resolve(cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) {
|
||||||
|
return confirmNumber(provisionMessage.number).then(function() {
|
||||||
|
return createAccount(
|
||||||
|
provisionMessage.number,
|
||||||
|
provisionMessage.provisioningCode,
|
||||||
|
provisionMessage.identityKeyPair,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
console.log('Unknown websocket message', request.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39584,22 +39600,21 @@ window.textsecure.messaging = function() {
|
||||||
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
||||||
}
|
}
|
||||||
|
|
||||||
var registrationId = deviceObjectList[i].registrationId;
|
|
||||||
if (registrationId === undefined) // ie this isnt a first-send-keyful deviceObject
|
|
||||||
registrationId = textsecure.storage.sessions.getSessionsForNumber(deviceObjectList[i].encodedNumber).registrationId;
|
|
||||||
return textsecure.protocol_wrapper.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
|
return textsecure.protocol_wrapper.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
|
||||||
textsecure.storage.devices.removeTempKeysFromDevice(deviceObjectList[i].encodedNumber);
|
return textsecure.protocol_wrapper.getRegistrationId(deviceObjectList[i].encodedNumber).then(function(registrationId) {
|
||||||
|
textsecure.storage.devices.removeTempKeysFromDevice(deviceObjectList[i].encodedNumber);
|
||||||
|
|
||||||
jsonData[i] = {
|
jsonData[i] = {
|
||||||
type: encryptedMsg.type,
|
type: encryptedMsg.type,
|
||||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||||
destinationRegistrationId: registrationId,
|
destinationRegistrationId: registrationId,
|
||||||
body: encryptedMsg.body,
|
body: encryptedMsg.body,
|
||||||
timestamp: timestamp
|
timestamp: timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deviceObjectList[i].relay !== undefined)
|
if (deviceObjectList[i].relay !== undefined)
|
||||||
jsonData[i].relay = deviceObjectList[i].relay;
|
jsonData[i].relay = deviceObjectList[i].relay;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39615,37 +39630,37 @@ window.textsecure.messaging = function() {
|
||||||
groupId = getString(groupId);
|
groupId = getString(groupId);
|
||||||
|
|
||||||
var doUpdate = false;
|
var doUpdate = false;
|
||||||
for (var i in devicesForNumber) {
|
Promise.all(devicesForNumber.map(function(device) {
|
||||||
var registrationId = deviceObjectList[i].registrationId;
|
return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
|
||||||
if (registrationId === undefined) // ie this isnt a first-send-keyful deviceObject
|
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId))
|
||||||
registrationId = textsecure.storage.sessions.getSessionsForNumber(deviceObjectList[i].encodedNumber).registrationId;
|
doUpdate = true;
|
||||||
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId))
|
|
||||||
doUpdate = true;
|
|
||||||
}
|
|
||||||
if (!doUpdate)
|
|
||||||
return Promise.resolve(true);
|
|
||||||
|
|
||||||
var group = textsecure.storage.groups.getGroup(groupId);
|
|
||||||
var numberIndex = group.numbers.indexOf(number);
|
|
||||||
if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
|
|
||||||
return Promise.reject("Tried to refresh group to non-member");
|
|
||||||
|
|
||||||
var proto = new textsecure.protobuf.PushMessageContent();
|
|
||||||
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
|
|
||||||
|
|
||||||
proto.group.id = toArrayBuffer(group.id);
|
|
||||||
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
|
|
||||||
proto.group.members = group.numbers;
|
|
||||||
proto.group.name = group.name === undefined ? null : group.name;
|
|
||||||
|
|
||||||
if (group.avatar !== undefined) {
|
|
||||||
return makeAttachmentPointer(group.avatar).then(function(attachment) {
|
|
||||||
proto.group.avatar = attachment;
|
|
||||||
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
|
||||||
});
|
});
|
||||||
} else {
|
})).then(function() {
|
||||||
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
if (!doUpdate)
|
||||||
}
|
return Promise.resolve(true);
|
||||||
|
|
||||||
|
var group = textsecure.storage.groups.getGroup(groupId);
|
||||||
|
var numberIndex = group.numbers.indexOf(number);
|
||||||
|
if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
|
||||||
|
return Promise.reject("Tried to refresh group to non-member");
|
||||||
|
|
||||||
|
var proto = new textsecure.protobuf.PushMessageContent();
|
||||||
|
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
|
||||||
|
|
||||||
|
proto.group.id = toArrayBuffer(group.id);
|
||||||
|
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
|
||||||
|
proto.group.members = group.numbers;
|
||||||
|
proto.group.name = group.name === undefined ? null : group.name;
|
||||||
|
|
||||||
|
if (group.avatar !== undefined) {
|
||||||
|
return makeAttachmentPointer(group.avatar).then(function(attachment) {
|
||||||
|
proto.group.avatar = attachment;
|
||||||
|
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var tryMessageAgain = function(number, encodedMessage, timestamp) {
|
var tryMessageAgain = function(number, encodedMessage, timestamp) {
|
||||||
|
@ -39730,7 +39745,7 @@ window.textsecure.messaging = function() {
|
||||||
|
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for (var j in devicesForNumber)
|
for (var j in devicesForNumber)
|
||||||
if (!textsecure.storage.sessions.haveOpenSessionForDevice(devicesForNumber[j].encodedNumber))
|
if (!textsecure.protocol_wrapper.hasOpenSession(devicesForNumber[j].encodedNumber))
|
||||||
promises[promises.length] = getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(devicesForNumber[j].encodedNumber)[1])]);
|
promises[promises.length] = getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(devicesForNumber[j].encodedNumber)[1])]);
|
||||||
|
|
||||||
Promise.all(promises).then(function() {
|
Promise.all(promises).then(function() {
|
||||||
|
|
100
js/options.js
100
js/options.js
|
@ -35,78 +35,54 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setProvisioningUrl(url) {
|
||||||
|
$('#status').text('');
|
||||||
|
new QRCode($('#qr')[0]).makeCode(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmNumber(number) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
$('#qr').hide();
|
||||||
|
$('.confirmation-dialog .number').text(number);
|
||||||
|
$('.confirmation-dialog .cancel').click(function(e) {
|
||||||
|
localStorage.clear();
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
$('.confirmation-dialog .ok').click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
$('.confirmation-dialog').hide();
|
||||||
|
$('.progress-dialog').show();
|
||||||
|
$('.progress-dialog .status').text('Registering new device...');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
$('.modal-container').show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var counter = 0;
|
||||||
|
function incrementCounter() {
|
||||||
|
$('.progress-dialog .bar').css('width', (++counter * 100 / 100) + '%');
|
||||||
|
}
|
||||||
|
|
||||||
$('.modal-container .cancel').click(function() {
|
$('.modal-container .cancel').click(function() {
|
||||||
$('.modal-container').hide();
|
$('.modal-container').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
if (textsecure.registration.isDone()) {
|
var bg = extension.windows.getBackground();
|
||||||
$('#complete-number').text(textsecure.storage.user.getNumber());
|
if (bg.textsecure.registration.isDone()) {
|
||||||
|
$('#complete-number').text(bg.textsecure.storage.user.getNumber());
|
||||||
$('#setup-complete').show().addClass('in');
|
$('#setup-complete').show().addClass('in');
|
||||||
initOptions();
|
initOptions();
|
||||||
} else {
|
} else {
|
||||||
$('#init-setup').show().addClass('in');
|
$('#init-setup').show().addClass('in');
|
||||||
$('#status').text("Connecting...");
|
$('#status').text("Connecting...");
|
||||||
textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) {
|
|
||||||
var qrCode = new QRCode(document.getElementById('qr'));
|
bg.textsecure.registerSecondDevice(setProvisioningUrl, confirmNumber, incrementCounter).then(function() {
|
||||||
var socket = textsecure.api.getTempWebsocket();
|
$('.modal-container').hide();
|
||||||
new WebSocketResource(socket, function(request) {
|
$('#init-setup').hide();
|
||||||
if (request.path == "/v1/address" && request.verb == "PUT") {
|
$('#setup-complete').show().addClass('in');
|
||||||
var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body);
|
initOptions();
|
||||||
var url = [ 'tsdevice:/', '?uuid=', proto.uuid, '&pub_key=',
|
|
||||||
encodeURIComponent(btoa(String.fromCharCode.apply(null, new Uint8Array(cryptoInfo.pubKey)))) ].join('');
|
|
||||||
$('#status').text('');
|
|
||||||
qrCode.makeCode(url);
|
|
||||||
request.respond(200, 'OK');
|
|
||||||
} else if (request.path == "/v1/message" && request.verb == "PUT") {
|
|
||||||
var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary');
|
|
||||||
cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) {
|
|
||||||
$('.confirmation-dialog .number').text(provisionMessage.number);
|
|
||||||
$('.confirmation-dialog .cancel').click(function(e) {
|
|
||||||
localStorage.clear();
|
|
||||||
});
|
|
||||||
$('.confirmation-dialog .ok').click(function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
$('.confirmation-dialog').hide();
|
|
||||||
$('.progress-dialog').show();
|
|
||||||
$('.progress-dialog .status').text('Registering new device...');
|
|
||||||
window.textsecure.registerSecondDevice(provisionMessage).then(function() {
|
|
||||||
$('.progress-dialog .status').text('Generating keys...');
|
|
||||||
var counter = 0;
|
|
||||||
var myWorker = new Worker('/js/key_worker.js');
|
|
||||||
myWorker.postMessage({
|
|
||||||
maxPreKeyId: textsecure.storage.get("maxPreKeyId", 0),
|
|
||||||
signedKeyId: textsecure.storage.get("signedKeyId", 0),
|
|
||||||
libaxolotl25519KeyidentityKey: textsecure.storage.get("libaxolotl25519KeyidentityKey"),
|
|
||||||
});
|
|
||||||
myWorker.onmessage = function(e) {
|
|
||||||
switch(e.data.method) {
|
|
||||||
case 'set':
|
|
||||||
textsecure.storage.put(e.data.key, e.data.value);
|
|
||||||
counter = counter + 1;
|
|
||||||
$('.progress-dialog .bar').css('width', (counter * 100 / 105) + '%');
|
|
||||||
break;
|
|
||||||
case 'remove':
|
|
||||||
textsecure.storage.remove(e.data.key);
|
|
||||||
break;
|
|
||||||
case 'done':
|
|
||||||
$('.progress-dialog .status').text('Uploading keys...');
|
|
||||||
textsecure.api.registerKeys(e.data.keys).then(function() {
|
|
||||||
textsecure.registration.done();
|
|
||||||
$('.modal-container').hide();
|
|
||||||
$('#init-setup').hide();
|
|
||||||
$('#setup-complete').show().addClass('in');
|
|
||||||
initOptions();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$('.modal-container').show();
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
console.log(request.path);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
;(function() {
|
;(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
var bg = extension.windows.getBackground();
|
||||||
|
|
||||||
function log(s) {
|
function log(s) {
|
||||||
console.log(s);
|
console.log(s);
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
var phoneView = new Whisper.PhoneInputView({el: $('#phone-number-input')});
|
var phoneView = new Whisper.PhoneInputView({el: $('#phone-number-input')});
|
||||||
phoneView.$el.find('input.number').intlTelInput();
|
phoneView.$el.find('input.number').intlTelInput();
|
||||||
|
|
||||||
var number = textsecure.storage.user.getNumber();
|
var number = bg.textsecure.storage.user.getNumber();
|
||||||
if (number) {
|
if (number) {
|
||||||
$('input.number').val(number);
|
$('input.number').val(number);
|
||||||
}
|
}
|
||||||
|
@ -60,7 +61,7 @@
|
||||||
$('#error').hide();
|
$('#error').hide();
|
||||||
var number = phoneView.validateNumber();
|
var number = phoneView.validateNumber();
|
||||||
if (number) {
|
if (number) {
|
||||||
textsecure.api.requestVerificationVoice(number).catch(displayError);
|
bg.textsecure.api.requestVerificationVoice(number).catch(displayError);
|
||||||
$('#step2').addClass('in').fadeIn();
|
$('#step2').addClass('in').fadeIn();
|
||||||
} else {
|
} else {
|
||||||
$('#number-container').addClass('invalid');
|
$('#number-container').addClass('invalid');
|
||||||
|
@ -71,7 +72,7 @@
|
||||||
$('#error').hide();
|
$('#error').hide();
|
||||||
var number = phoneView.validateNumber();
|
var number = phoneView.validateNumber();
|
||||||
if (number) {
|
if (number) {
|
||||||
textsecure.api.requestVerificationSMS(number).catch(displayError);
|
bg.textsecure.api.requestVerificationSMS(number).catch(displayError);
|
||||||
$('#step2').addClass('in').fadeIn();
|
$('#step2').addClass('in').fadeIn();
|
||||||
} else {
|
} else {
|
||||||
$('#number-container').addClass('invalid');
|
$('#number-container').addClass('invalid');
|
||||||
|
@ -80,40 +81,14 @@
|
||||||
|
|
||||||
$('#form').submit(function(e) {
|
$('#form').submit(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
log('registering');
|
|
||||||
var number = phoneView.validateNumber();
|
var number = phoneView.validateNumber();
|
||||||
var verificationCode = $('#code').val().replace(/\D+/g, "");
|
var verificationCode = $('#code').val().replace(/\D+/g, "");
|
||||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
|
||||||
|
|
||||||
var password = btoa(String.fromCharCode.apply(null, new Uint8Array(textsecure.crypto.getRandomBytes(16))));
|
|
||||||
password = password.substring(0, password.length - 2);
|
|
||||||
|
|
||||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
|
||||||
registrationId = registrationId & 0x3fff;
|
|
||||||
|
|
||||||
log('clearing data');
|
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
|
||||||
localStorage.setItem('first_install_ran', 1);
|
localStorage.setItem('first_install_ran', 1);
|
||||||
textsecure.storage.put('registrationId', registrationId);
|
bg.textsecure.registerSingleDevice(number, verificationCode).then(function() {
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
extension.navigator.tabs.create("options.html");
|
||||||
textsecure.storage.put('password', password);
|
window.close();
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, 1);
|
|
||||||
textsecure.storage.put('regionCode', libphonenumber.util.getRegionCodeForNumber(number));
|
|
||||||
|
|
||||||
log('verifying code');
|
|
||||||
return textsecure.api.confirmCode(
|
|
||||||
number, verificationCode, password, signalingKey, registrationId, true
|
|
||||||
).then(function() {
|
|
||||||
log('generating keys');
|
|
||||||
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
|
|
||||||
log('uploading keys');
|
|
||||||
return textsecure.api.registerKeys(keys).then(function() {
|
|
||||||
textsecure.registration.done();
|
|
||||||
log('done');
|
|
||||||
chrome.runtime.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
log(e);
|
log(e);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,44 +1,9 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
;(function() {
|
;(function() {
|
||||||
var axolotlInstance = axolotl.protocol({
|
'use strict';
|
||||||
getMyRegistrationId: function() {
|
window.textsecure = window.textsecure || {};
|
||||||
return textsecure.storage.get("registrationId");
|
window.textsecure.storage = window.textsecure.storage || {};
|
||||||
},
|
textsecure.storage.axolotl = new AxolotlStore();
|
||||||
put: function(key, value) {
|
var axolotlInstance = axolotl.protocol(textsecure.storage.axolotl);
|
||||||
return textsecure.storage.put("libaxolotl" + key, value);
|
|
||||||
},
|
|
||||||
get: function(key, defaultValue) {
|
|
||||||
return textsecure.storage.get("libaxolotl" + key, defaultValue);
|
|
||||||
},
|
|
||||||
remove: function(key) {
|
|
||||||
return textsecure.storage.remove("libaxolotl" + key);
|
|
||||||
},
|
|
||||||
|
|
||||||
identityKeys: {
|
|
||||||
get: function(identifier) {
|
|
||||||
return textsecure.storage.devices.getIdentityKeyForNumber(textsecure.utils.unencodeNumber(identifier)[0]);
|
|
||||||
},
|
|
||||||
put: function(identifier, identityKey) {
|
|
||||||
return textsecure.storage.devices.checkSaveIdentityKeyForNumber(textsecure.utils.unencodeNumber(identifier)[0], identityKey);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
sessions: {
|
|
||||||
get: function(identifier) {
|
|
||||||
return textsecure.storage.sessions.getSessionsForNumber(identifier);
|
|
||||||
},
|
|
||||||
put: function(identifier, record) {
|
|
||||||
return textsecure.storage.sessions.putSessionsForDevice(identifier, record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(keys) {
|
|
||||||
return textsecure.api.registerKeys(keys).catch(function(e) {
|
|
||||||
//TODO: Notify the user somehow?
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var decodeMessageContents = function(res) {
|
var decodeMessageContents = function(res) {
|
||||||
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
|
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
|
||||||
|
@ -53,7 +18,7 @@
|
||||||
|
|
||||||
var handlePreKeyWhisperMessage = function(from, message) {
|
var handlePreKeyWhisperMessage = function(from, message) {
|
||||||
try {
|
try {
|
||||||
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, message);
|
return axolotlInstance.handlePreKeyWhisperMessage(from, message);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if (e.message === 'Unknown identity key') {
|
if (e.message === 'Unknown identity key') {
|
||||||
// create an error that the UI will pick up and ask the
|
// create an error that the UI will pick up and ask the
|
||||||
|
@ -72,7 +37,7 @@
|
||||||
return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message));
|
return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message));
|
||||||
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
|
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
|
||||||
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice);
|
||||||
return textsecure.protocol_wrapper.decryptWhisperMessage(from, getString(proto.message)).then(decodeMessageContents);
|
return axolotlInstance.decryptWhisperMessage(from, getString(proto.message)).then(decodeMessageContents);
|
||||||
case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE:
|
case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE:
|
||||||
if (proto.message.readUint8() != ((3 << 4) | 3))
|
if (proto.message.readUint8() != ((3 << 4) | 3))
|
||||||
throw new Error("Bad version byte");
|
throw new Error("Bad version byte");
|
||||||
|
@ -87,25 +52,32 @@
|
||||||
closeOpenSessionForDevice: function(encodedNumber) {
|
closeOpenSessionForDevice: function(encodedNumber) {
|
||||||
return axolotlInstance.closeOpenSessionForDevice(encodedNumber)
|
return axolotlInstance.closeOpenSessionForDevice(encodedNumber)
|
||||||
},
|
},
|
||||||
decryptWhisperMessage: function(encodedNumber, messageBytes, session) {
|
|
||||||
return axolotlInstance.decryptWhisperMessage(encodedNumber, messageBytes, session);
|
|
||||||
},
|
|
||||||
handlePreKeyWhisperMessage: function(from, encodedMessage) {
|
|
||||||
return axolotlInstance.handlePreKeyWhisperMessage(from, encodedMessage);
|
|
||||||
},
|
|
||||||
encryptMessageFor: function(deviceObject, pushMessageContent) {
|
encryptMessageFor: function(deviceObject, pushMessageContent) {
|
||||||
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
|
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
|
||||||
},
|
},
|
||||||
generateKeys: function() {
|
generateKeys: function(count, progressCallback) {
|
||||||
return axolotlInstance.generateKeys();
|
if (textsecure.worker_path) {
|
||||||
|
axolotlInstance.startWorker(textsecure.worker_path);
|
||||||
|
}
|
||||||
|
return generateKeys(count, progressCallback).then(function(result) {
|
||||||
|
axolotlInstance.stopWorker();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
createIdentityKeyRecvSocket: function() {
|
createIdentityKeyRecvSocket: function() {
|
||||||
return axolotlInstance.createIdentityKeyRecvSocket();
|
return axolotlInstance.createIdentityKeyRecvSocket();
|
||||||
|
},
|
||||||
|
hasOpenSession: function(encodedNumber) {
|
||||||
|
return axolotlInstance.hasOpenSession(encodedNumber);
|
||||||
|
},
|
||||||
|
getRegistrationId: function(encodedNumber) {
|
||||||
|
return axolotlInstance.getRegistrationId(encodedNumber);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var tryMessageAgain = function(from, encodedMessage) {
|
var tryMessageAgain = function(from, encodedMessage) {
|
||||||
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, encodedMessage).then(decodeMessageContents);
|
return axolotlInstance.handlePreKeyWhisperMessage(from, encodedMessage).then(decodeMessageContents);
|
||||||
}
|
}
|
||||||
textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.INIT_SESSION);
|
textsecure.replay.registerFunction(tryMessageAgain, textsecure.replay.Type.INIT_SESSION);
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -111,6 +111,8 @@ window.textsecure.utils = function() {
|
||||||
for (var key in thing)
|
for (var key in thing)
|
||||||
res[key] = ensureStringed(thing[key]);
|
res[key] = ensureStringed(thing[key]);
|
||||||
return res;
|
return res;
|
||||||
|
} else if (thing === null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
throw new Error("unsure of how to jsonify object of type " + typeof thing);
|
throw new Error("unsure of how to jsonify object of type " + typeof thing);
|
||||||
|
|
||||||
|
@ -244,9 +246,11 @@ textsecure.processDecrypted = function(decrypted, source) {
|
||||||
return Promise.all(promises).then(function() {
|
return Promise.all(promises).then(function() {
|
||||||
return decrypted;
|
return decrypted;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
function createAccount(number, verificationCode, identityKeyPair, single_device) {
|
||||||
|
textsecure.storage.put('identityKey', identityKeyPair);
|
||||||
|
|
||||||
window.textsecure.registerSingleDevice = function(number, verificationCode, stepDone) {
|
|
||||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
textsecure.storage.put('signaling_key', signalingKey);
|
||||||
|
|
||||||
|
@ -254,38 +258,94 @@ window.textsecure.registerSingleDevice = function(number, verificationCode, step
|
||||||
password = password.substring(0, password.length - 2);
|
password = password.substring(0, password.length - 2);
|
||||||
textsecure.storage.put("password", password);
|
textsecure.storage.put("password", password);
|
||||||
|
|
||||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
var registrationId = axolotl.util.generateRegistrationId();
|
||||||
registrationId = registrationId & 0x3fff;
|
|
||||||
textsecure.storage.put("registrationId", registrationId);
|
textsecure.storage.put("registrationId", registrationId);
|
||||||
|
|
||||||
return textsecure.api.confirmCode(number, verificationCode, password, signalingKey, registrationId, true).then(function() {
|
return textsecure.api.confirmCode(
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, 1);
|
number, verificationCode, password, signalingKey, registrationId, single_device
|
||||||
|
).then(function(response) {
|
||||||
|
textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1);
|
||||||
textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number));
|
textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(number));
|
||||||
stepDone(1);
|
|
||||||
|
|
||||||
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
|
return textsecure.protocol_wrapper.generateKeys().then(textsecure.registration.done);
|
||||||
stepDone(2);
|
});
|
||||||
return textsecure.api.registerKeys(keys).then(function() {
|
}
|
||||||
stepDone(3);
|
|
||||||
|
function generateKeys(count, progressCallback) {
|
||||||
|
if (count === undefined) {
|
||||||
|
throw TypeError('generateKeys: count is undefined');
|
||||||
|
}
|
||||||
|
if (typeof progressCallback !== 'function') {
|
||||||
|
progressCallback = undefined;
|
||||||
|
}
|
||||||
|
var store = textsecure.storage.axolotl;
|
||||||
|
var identityKey = store.getMyIdentityKey();
|
||||||
|
var result = { preKeys: [], identityKey: identityKey.pubKey };
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
var startId = textsecure.storage.get('maxPreKeyId', 1);
|
||||||
|
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||||
|
|
||||||
|
for (var keyId = startId; keyId < startId+count; ++keyId) {
|
||||||
|
promises.push(
|
||||||
|
axolotl.util.generatePreKey(keyId).then(function(res) {
|
||||||
|
store.putPreKey(res.keyId, res.keyPair);
|
||||||
|
result.preKeys.push({
|
||||||
|
keyId : res.keyId,
|
||||||
|
publicKey : res.keyPair.pubKey
|
||||||
|
});
|
||||||
|
if (progressCallback) { progressCallback(); }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
promises.push(
|
||||||
|
axolotl.util.generateSignedPreKey(identityKey, signedKeyId).then(function(res) {
|
||||||
|
store.putSignedPreKey(res.keyId, res.keyPair);
|
||||||
|
result.signedPreKey = {
|
||||||
|
keyId : res.keyId,
|
||||||
|
publicKey : res.keyPair.pubKey,
|
||||||
|
signature : res.signature
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
store.removeSignedPreKey(signedKeyId - 2);
|
||||||
|
textsecure.storage.put('maxPreKeyId', startId + count);
|
||||||
|
textsecure.storage.put('signedKeyId', signedKeyId + 1);
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function() {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.textsecure.registerSecondDevice = function(setProvisioningUrl, confirmNumber, progressCallback) {
|
||||||
|
return textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
new WebSocketResource(textsecure.api.getTempWebsocket(), function(request) {
|
||||||
|
if (request.path == "/v1/address" && request.verb == "PUT") {
|
||||||
|
var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body);
|
||||||
|
setProvisioningUrl([
|
||||||
|
'tsdevice:/?uuid=', proto.uuid, '&pub_key=',
|
||||||
|
encodeURIComponent(btoa(getString(cryptoInfo.pubKey)))
|
||||||
|
].join(''));
|
||||||
|
request.respond(200, 'OK');
|
||||||
|
} else if (request.path == "/v1/message" && request.verb == "PUT") {
|
||||||
|
var envelope = textsecure.protobuf.ProvisionEnvelope.decode(request.body, 'binary');
|
||||||
|
request.respond(200, 'OK');
|
||||||
|
resolve(cryptoInfo.decryptAndHandleDeviceInit(envelope).then(function(provisionMessage) {
|
||||||
|
return confirmNumber(provisionMessage.number).then(function() {
|
||||||
|
return createAccount(
|
||||||
|
provisionMessage.number,
|
||||||
|
provisionMessage.provisioningCode,
|
||||||
|
provisionMessage.identityKeyPair,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
console.log('Unknown websocket message', request.path);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
window.textsecure.registerSecondDevice = function(provisionMessage) {
|
|
||||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
|
||||||
|
|
||||||
var password = btoa(getString(textsecure.crypto.getRandomBytes(16)));
|
|
||||||
password = password.substring(0, password.length - 2);
|
|
||||||
textsecure.storage.put("password", password);
|
|
||||||
|
|
||||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
|
||||||
registrationId = registrationId & 0x3fff;
|
|
||||||
textsecure.storage.put("registrationId", registrationId);
|
|
||||||
|
|
||||||
return textsecure.api.confirmCode(provisionMessage.number, provisionMessage.provisioningCode, password, signalingKey, registrationId, false).then(function(result) {
|
|
||||||
textsecure.storage.user.setNumberAndDeviceId(provisionMessage.number, result.deviceId);
|
|
||||||
textsecure.storage.put("regionCode", libphonenumber.util.getRegionCodeForNumber(provisionMessage.number));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,22 +67,21 @@ window.textsecure.messaging = function() {
|
||||||
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
return new Promise(function() { throw new Error("Mismatched relays for number " + number); });
|
||||||
}
|
}
|
||||||
|
|
||||||
var registrationId = deviceObjectList[i].registrationId;
|
|
||||||
if (registrationId === undefined) // ie this isnt a first-send-keyful deviceObject
|
|
||||||
registrationId = textsecure.storage.sessions.getSessionsForNumber(deviceObjectList[i].encodedNumber).registrationId;
|
|
||||||
return textsecure.protocol_wrapper.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
|
return textsecure.protocol_wrapper.encryptMessageFor(deviceObjectList[i], message).then(function(encryptedMsg) {
|
||||||
textsecure.storage.devices.removeTempKeysFromDevice(deviceObjectList[i].encodedNumber);
|
return textsecure.protocol_wrapper.getRegistrationId(deviceObjectList[i].encodedNumber).then(function(registrationId) {
|
||||||
|
textsecure.storage.devices.removeTempKeysFromDevice(deviceObjectList[i].encodedNumber);
|
||||||
|
|
||||||
jsonData[i] = {
|
jsonData[i] = {
|
||||||
type: encryptedMsg.type,
|
type: encryptedMsg.type,
|
||||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||||
destinationRegistrationId: registrationId,
|
destinationRegistrationId: registrationId,
|
||||||
body: encryptedMsg.body,
|
body: encryptedMsg.body,
|
||||||
timestamp: timestamp
|
timestamp: timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deviceObjectList[i].relay !== undefined)
|
if (deviceObjectList[i].relay !== undefined)
|
||||||
jsonData[i].relay = deviceObjectList[i].relay;
|
jsonData[i].relay = deviceObjectList[i].relay;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,37 +97,37 @@ window.textsecure.messaging = function() {
|
||||||
groupId = getString(groupId);
|
groupId = getString(groupId);
|
||||||
|
|
||||||
var doUpdate = false;
|
var doUpdate = false;
|
||||||
for (var i in devicesForNumber) {
|
Promise.all(devicesForNumber.map(function(device) {
|
||||||
var registrationId = deviceObjectList[i].registrationId;
|
return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
|
||||||
if (registrationId === undefined) // ie this isnt a first-send-keyful deviceObject
|
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId))
|
||||||
registrationId = textsecure.storage.sessions.getSessionsForNumber(deviceObjectList[i].encodedNumber).registrationId;
|
doUpdate = true;
|
||||||
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId))
|
|
||||||
doUpdate = true;
|
|
||||||
}
|
|
||||||
if (!doUpdate)
|
|
||||||
return Promise.resolve(true);
|
|
||||||
|
|
||||||
var group = textsecure.storage.groups.getGroup(groupId);
|
|
||||||
var numberIndex = group.numbers.indexOf(number);
|
|
||||||
if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
|
|
||||||
return Promise.reject("Tried to refresh group to non-member");
|
|
||||||
|
|
||||||
var proto = new textsecure.protobuf.PushMessageContent();
|
|
||||||
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
|
|
||||||
|
|
||||||
proto.group.id = toArrayBuffer(group.id);
|
|
||||||
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
|
|
||||||
proto.group.members = group.numbers;
|
|
||||||
proto.group.name = group.name === undefined ? null : group.name;
|
|
||||||
|
|
||||||
if (group.avatar !== undefined) {
|
|
||||||
return makeAttachmentPointer(group.avatar).then(function(attachment) {
|
|
||||||
proto.group.avatar = attachment;
|
|
||||||
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
|
||||||
});
|
});
|
||||||
} else {
|
})).then(function() {
|
||||||
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
if (!doUpdate)
|
||||||
}
|
return Promise.resolve(true);
|
||||||
|
|
||||||
|
var group = textsecure.storage.groups.getGroup(groupId);
|
||||||
|
var numberIndex = group.numbers.indexOf(number);
|
||||||
|
if (numberIndex < 0) // This is potentially a multi-message rare racing-AJAX race
|
||||||
|
return Promise.reject("Tried to refresh group to non-member");
|
||||||
|
|
||||||
|
var proto = new textsecure.protobuf.PushMessageContent();
|
||||||
|
proto.group = new textsecure.protobuf.PushMessageContent.GroupContext();
|
||||||
|
|
||||||
|
proto.group.id = toArrayBuffer(group.id);
|
||||||
|
proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE;
|
||||||
|
proto.group.members = group.numbers;
|
||||||
|
proto.group.name = group.name === undefined ? null : group.name;
|
||||||
|
|
||||||
|
if (group.avatar !== undefined) {
|
||||||
|
return makeAttachmentPointer(group.avatar).then(function(attachment) {
|
||||||
|
proto.group.avatar = attachment;
|
||||||
|
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var tryMessageAgain = function(number, encodedMessage, timestamp) {
|
var tryMessageAgain = function(number, encodedMessage, timestamp) {
|
||||||
|
@ -213,7 +212,7 @@ window.textsecure.messaging = function() {
|
||||||
|
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for (var j in devicesForNumber)
|
for (var j in devicesForNumber)
|
||||||
if (!textsecure.storage.sessions.haveOpenSessionForDevice(devicesForNumber[j].encodedNumber))
|
if (!textsecure.protocol_wrapper.hasOpenSession(devicesForNumber[j].encodedNumber))
|
||||||
promises[promises.length] = getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(devicesForNumber[j].encodedNumber)[1])]);
|
promises[promises.length] = getKeysForNumber(number, [parseInt(textsecure.utils.unencodeNumber(devicesForNumber[j].encodedNumber)[1])]);
|
||||||
|
|
||||||
Promise.all(promises).then(function() {
|
Promise.all(promises).then(function() {
|
||||||
|
|
|
@ -34,46 +34,30 @@
|
||||||
if (sessions[deviceId] === undefined)
|
if (sessions[deviceId] === undefined)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
var record = new axolotl.sessions.RecipientRecord();
|
return sessions[deviceId];
|
||||||
record.deserialize(sessions[deviceId]);
|
|
||||||
if (getString(textsecure.storage.devices.getIdentityKeyForNumber(number)) !== getString(record.identityKey))
|
|
||||||
throw new Error("Got mismatched identity key on device object load");
|
|
||||||
return record;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
putSessionsForDevice: function(encodedNumber, record) {
|
putSessionsForDevice: function(encodedNumber, record) {
|
||||||
var number = textsecure.utils.unencodeNumber(encodedNumber)[0];
|
var number = textsecure.utils.unencodeNumber(encodedNumber)[0];
|
||||||
var deviceId = textsecure.utils.unencodeNumber(encodedNumber)[1];
|
var deviceId = textsecure.utils.unencodeNumber(encodedNumber)[1];
|
||||||
|
|
||||||
textsecure.storage.devices.checkSaveIdentityKeyForNumber(number, record.identityKey);
|
|
||||||
|
|
||||||
var sessions = textsecure.storage.get("sessions" + number);
|
var sessions = textsecure.storage.get("sessions" + number);
|
||||||
if (sessions === undefined)
|
if (sessions === undefined)
|
||||||
sessions = {};
|
sessions = {};
|
||||||
sessions[deviceId] = record.serialize();
|
sessions[deviceId] = record;
|
||||||
textsecure.storage.put("sessions" + number, sessions);
|
textsecure.storage.put("sessions" + number, sessions);
|
||||||
|
|
||||||
var device = textsecure.storage.devices.getDeviceObject(encodedNumber);
|
var device = textsecure.storage.devices.getDeviceObject(encodedNumber);
|
||||||
if (device === undefined) {
|
if (device === undefined) {
|
||||||
|
var identityKey = textsecure.storage.devices.getIdentityKeyForNumber(number);
|
||||||
device = { encodedNumber: encodedNumber,
|
device = { encodedNumber: encodedNumber,
|
||||||
//TODO: Remove this duplication
|
//TODO: Remove this duplication
|
||||||
identityKey: record.identityKey
|
identityKey: identityKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (getString(device.identityKey) !== getString(record.identityKey)) {
|
|
||||||
console.error("Got device object with key inconsistent after checkSaveIdentityKeyForNumber returned!");
|
|
||||||
throw new Error("Tried to put session for device with changed identity key");
|
|
||||||
}
|
|
||||||
return textsecure.storage.devices.saveDeviceObject(device);
|
return textsecure.storage.devices.saveDeviceObject(device);
|
||||||
},
|
},
|
||||||
|
|
||||||
haveOpenSessionForDevice: function(encodedNumber) {
|
|
||||||
var sessions = textsecure.storage.sessions.getSessionsForNumber(encodedNumber);
|
|
||||||
if (sessions === undefined || !sessions.haveOpenSession())
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use textsecure.storage.devices.removeIdentityKeyForNumber (which calls this) instead
|
// Use textsecure.storage.devices.removeIdentityKeyForNumber (which calls this) instead
|
||||||
_removeIdentityKeyForNumber: function(number) {
|
_removeIdentityKeyForNumber: function(number) {
|
||||||
textsecure.storage.remove("sessions" + number);
|
textsecure.storage.remove("sessions" + number);
|
||||||
|
|
181
libtextsecure/test/generate_keys_test.js
Normal file
181
libtextsecure/test/generate_keys_test.js
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/* vim: ts=4:sw=4
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("Key generation", function() {
|
||||||
|
var count = 10;
|
||||||
|
this.timeout(count*1000);
|
||||||
|
|
||||||
|
function validateStoredKeyPair(keyPair) {
|
||||||
|
/* Ensure the keypair matches the format used internally by libaxolotl */
|
||||||
|
assert.isObject(keyPair, 'Stored keyPair is not an object');
|
||||||
|
assert.instanceOf(keyPair.pubKey, ArrayBuffer);
|
||||||
|
assert.instanceOf(keyPair.privKey, ArrayBuffer);
|
||||||
|
assert.strictEqual(keyPair.pubKey.byteLength, 33);
|
||||||
|
assert.strictEqual(new Uint8Array(keyPair.pubKey)[0], 5);
|
||||||
|
assert.strictEqual(keyPair.privKey.byteLength, 32);
|
||||||
|
}
|
||||||
|
function itStoresPreKey(keyId) {
|
||||||
|
it('prekey ' + keyId + ' is valid', function(done) {
|
||||||
|
return textsecure.storage.axolotl.getPreKey(keyId).then(function(keyPair) {
|
||||||
|
validateStoredKeyPair(keyPair);
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function itStoresSignedPreKey(keyId) {
|
||||||
|
it('signed prekey ' + keyId + ' is valid', function(done) {
|
||||||
|
return textsecure.storage.axolotl.getSignedPreKey(keyId).then(function(keyPair) {
|
||||||
|
validateStoredKeyPair(keyPair);
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function validateResultKey(resultKey) {
|
||||||
|
return textsecure.storage.axolotl.getPreKey(resultKey.keyId).then(function(keyPair) {
|
||||||
|
assertEqualArrayBuffers(resultKey.publicKey, keyPair.pubKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function validateResultSignedKey(resultSignedKey) {
|
||||||
|
return textsecure.storage.axolotl.getSignedPreKey(resultSignedKey.keyId).then(function(keyPair) {
|
||||||
|
assertEqualArrayBuffers(resultSignedKey.publicKey, keyPair.pubKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
before(function(done) {
|
||||||
|
localStorage.clear();
|
||||||
|
axolotl.util.generateIdentityKeyPair().then(function(keyPair) {
|
||||||
|
return textsecure.storage.axolotl.put('identityKey', keyPair);
|
||||||
|
}).then(done, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('the first time', function() {
|
||||||
|
var result;
|
||||||
|
/* result should have this format
|
||||||
|
* {
|
||||||
|
* preKeys: [ { keyId, publicKey }, ... ],
|
||||||
|
* signedPreKey: { keyId, publicKey, signature },
|
||||||
|
* identityKey: <ArrayBuffer>
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
before(function(done) {
|
||||||
|
generateKeys(count).then(function(res) {
|
||||||
|
result = res;
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
for (var i = 1; i <= count; i++) {
|
||||||
|
itStoresPreKey(i);
|
||||||
|
}
|
||||||
|
itStoresSignedPreKey(1);
|
||||||
|
|
||||||
|
it('result contains ' + count + ' preKeys', function() {
|
||||||
|
assert.isArray(result.preKeys);
|
||||||
|
assert.lengthOf(result.preKeys, count);
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
assert.isObject(result.preKeys[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('result contains the correct keyIds', function() {
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
assert.strictEqual(result.preKeys[i].keyId, i+1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('result contains the correct public keys', function(done) {
|
||||||
|
Promise.all(result.preKeys.map(validateResultKey)).then(function() {
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it('returns a signed prekey', function(done) {
|
||||||
|
assert.strictEqual(result.signedPreKey.keyId, 1);
|
||||||
|
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||||
|
validateResultSignedKey(result.signedPreKey).then(done,done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('the second time', function() {
|
||||||
|
var result;
|
||||||
|
before(function(done) {
|
||||||
|
generateKeys(count).then(function(res) {
|
||||||
|
result = res;
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
for (var i = 1; i <= 2*count; i++) {
|
||||||
|
itStoresPreKey(i);
|
||||||
|
}
|
||||||
|
itStoresSignedPreKey(1);
|
||||||
|
itStoresSignedPreKey(2);
|
||||||
|
it('result contains ' + count + ' preKeys', function() {
|
||||||
|
assert.isArray(result.preKeys);
|
||||||
|
assert.lengthOf(result.preKeys, count);
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
assert.isObject(result.preKeys[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('result contains the correct keyIds', function() {
|
||||||
|
for (var i = 1; i <= count; i++) {
|
||||||
|
assert.strictEqual(result.preKeys[i-1].keyId, i+count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('result contains the correct public keys', function(done) {
|
||||||
|
Promise.all(result.preKeys.map(validateResultKey)).then(function() {
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it('returns a signed prekey', function(done) {
|
||||||
|
assert.strictEqual(result.signedPreKey.keyId, 2);
|
||||||
|
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||||
|
validateResultSignedKey(result.signedPreKey).then(done,done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('the third time', function() {
|
||||||
|
var result;
|
||||||
|
before(function(done) {
|
||||||
|
generateKeys(count).then(function(res) {
|
||||||
|
result = res;
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
for (var i = 1; i <= 3*count; i++) {
|
||||||
|
itStoresPreKey(i);
|
||||||
|
}
|
||||||
|
itStoresSignedPreKey(2);
|
||||||
|
itStoresSignedPreKey(3);
|
||||||
|
it('result contains ' + count + ' preKeys', function() {
|
||||||
|
assert.isArray(result.preKeys);
|
||||||
|
assert.lengthOf(result.preKeys, count);
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
assert.isObject(result.preKeys[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('result contains the correct keyIds', function() {
|
||||||
|
for (var i = 1; i <= count; i++) {
|
||||||
|
assert.strictEqual(result.preKeys[i-1].keyId, i+2*count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('result contains the correct public keys', function(done) {
|
||||||
|
Promise.all(result.preKeys.map(validateResultKey)).then(function() {
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
it('result contains a signed prekey', function(done) {
|
||||||
|
assert.strictEqual(result.signedPreKey.keyId, 3);
|
||||||
|
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||||
|
validateResultSignedKey(result.signedPreKey).then(done,done);
|
||||||
|
});
|
||||||
|
it('deletes signed key 1', function() {
|
||||||
|
textsecure.storage.axolotl.getSignedPreKey(1).then(function(keyPair) {
|
||||||
|
assert.isUndefined(keyPair);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
93
libtextsecure/test/in_memory_axolotl_store.js
Normal file
93
libtextsecure/test/in_memory_axolotl_store.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
function AxolotlStore() {
|
||||||
|
this.store = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AxolotlStore.prototype = {
|
||||||
|
getMyIdentityKey: function() {
|
||||||
|
return this.get('identityKey');
|
||||||
|
},
|
||||||
|
getMyRegistrationId: function() {
|
||||||
|
return this.get('registrationId');
|
||||||
|
},
|
||||||
|
put: function(key, value) {
|
||||||
|
if (key === undefined || value === undefined || key === null || value === null)
|
||||||
|
throw new Error("Tried to store undefined/null");
|
||||||
|
this.store[key] = value;
|
||||||
|
},
|
||||||
|
get: function(key, defaultValue) {
|
||||||
|
if (key === null || key === undefined)
|
||||||
|
throw new Error("Tried to get value for undefined/null key");
|
||||||
|
if (key in this.store) {
|
||||||
|
return this.store[key];
|
||||||
|
} else {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: function(key) {
|
||||||
|
if (key === null || key === undefined)
|
||||||
|
throw new Error("Tried to remove value for undefined/null key");
|
||||||
|
delete this.store[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
getIdentityKey: function(identifier) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to get identity key for undefined/null key");
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.get('identityKey' + identifier));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
putIdentityKey: function(identifier, identityKey) {
|
||||||
|
if (identifier === null || identifier === undefined)
|
||||||
|
throw new Error("Tried to put identity key for undefined/null key");
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.put('identityKey' + identifier, identityKey));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Returns a prekeypair object or undefined */
|
||||||
|
getPreKey: function(keyId) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
var res = this.get('25519KeypreKey' + keyId);
|
||||||
|
resolve(res);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
putPreKey: function(keyId, keyPair) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.put('25519KeypreKey' + keyId, keyPair));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
removePreKey: function(keyId) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.remove('25519KeypreKey' + keyId));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Returns a signed keypair object or undefined */
|
||||||
|
getSignedPreKey: function(keyId) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
var res = this.get('25519KeysignedKey' + keyId);
|
||||||
|
resolve(res);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
putSignedPreKey: function(keyId, keyPair) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.put('25519KeysignedKey' + keyId, keyPair));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
removeSignedPreKey: function(keyId) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.remove('25519KeysignedKey' + keyId));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
getSession: function(identifier) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.get('session' + identifier));
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
putSession: function(identifier, record) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(this.put('session' + identifier, record));
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
};
|
|
@ -28,9 +28,10 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="test.js"></script>
|
<script type="text/javascript" src="test.js"></script>
|
||||||
<script type="text/javascript" src="blanket_mocha.js"></script>
|
<script type="text/javascript" src="blanket_mocha.js"></script>
|
||||||
|
<script type="text/javascript" src="in_memory_axolotl_store.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../components.js"></script>
|
<script type="text/javascript" src="../components.js"></script>
|
||||||
|
<script type="text/javascript" src="../crypto.js"></script>
|
||||||
<script type="text/javascript" src="../protobufs.js" data-cover></script>
|
<script type="text/javascript" src="../protobufs.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../errors.js" data-cover></script>
|
<script type="text/javascript" src="../errors.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../storage.js" data-cover></script>
|
<script type="text/javascript" src="../storage.js" data-cover></script>
|
||||||
|
@ -49,6 +50,8 @@
|
||||||
<script type="text/javascript" src="helpers_test.js"></script>
|
<script type="text/javascript" src="helpers_test.js"></script>
|
||||||
<script type="text/javascript" src="websocket-resources_test.js"></script>
|
<script type="text/javascript" src="websocket-resources_test.js"></script>
|
||||||
<script type="text/javascript" src="protocol_test.js"></script>
|
<script type="text/javascript" src="protocol_test.js"></script>
|
||||||
|
<script type="text/javascript" src="storage_test.js"></script>
|
||||||
|
<script type="text/javascript" src="generate_keys_test.js"></script>
|
||||||
<script type="text/javascript" src="websocket_test.js"></script>
|
<script type="text/javascript" src="websocket_test.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
93
libtextsecure/test/storage_test.js
Normal file
93
libtextsecure/test/storage_test.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/* vim: ts=4:sw=4
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("AxolotlStore", function() {
|
||||||
|
before(function() { localStorage.clear(); });
|
||||||
|
var store = textsecure.storage.axolotl;
|
||||||
|
var identifier = '+5558675309';
|
||||||
|
var identityKey = {
|
||||||
|
pubKey: textsecure.crypto.getRandomBytes(33),
|
||||||
|
privKey: textsecure.crypto.getRandomBytes(32),
|
||||||
|
};
|
||||||
|
var testKey = {
|
||||||
|
pubKey: textsecure.crypto.getRandomBytes(33),
|
||||||
|
privKey: textsecure.crypto.getRandomBytes(32),
|
||||||
|
};
|
||||||
|
it('retrieves my registration id', function() {
|
||||||
|
store.put('registrationId', 1337);
|
||||||
|
var reg = store.getMyRegistrationId();
|
||||||
|
assert.strictEqual(reg, 1337);
|
||||||
|
});
|
||||||
|
it('retrieves my identity key', function() {
|
||||||
|
store.put('identityKey', identityKey);
|
||||||
|
var key = store.getMyIdentityKey();
|
||||||
|
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
||||||
|
});
|
||||||
|
it('stores identity keys', function(done) {
|
||||||
|
store.putIdentityKey(identifier, testKey.pubKey).then(function() {
|
||||||
|
return store.getIdentityKey(identifier).then(function(key) {
|
||||||
|
assertEqualArrayBuffers(key, testKey.pubKey);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('stores prekeys', function(done) {
|
||||||
|
store.putPreKey(1, testKey).then(function() {
|
||||||
|
return store.getPreKey(1).then(function(key) {
|
||||||
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('deletes prekeys', function(done) {
|
||||||
|
before(function(done) {
|
||||||
|
store.putPreKey(2, testKey).then(done);
|
||||||
|
});
|
||||||
|
store.removePreKey(2, testKey).then(function() {
|
||||||
|
return store.getPreKey(2).then(function(key) {
|
||||||
|
assert.isUndefined(key);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('stores signed prekeys', function(done) {
|
||||||
|
store.putSignedPreKey(3, testKey).then(function() {
|
||||||
|
return store.getSignedPreKey(3).then(function(key) {
|
||||||
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('deletes signed prekeys', function(done) {
|
||||||
|
before(function(done) {
|
||||||
|
store.putSignedPreKey(4, testKey).then(done);
|
||||||
|
});
|
||||||
|
store.removeSignedPreKey(4, testKey).then(function() {
|
||||||
|
return store.getSignedPreKey(4).then(function(key) {
|
||||||
|
assert.isUndefined(key);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('stores sessions', function(done) {
|
||||||
|
var testRecord = "an opaque string";
|
||||||
|
store.putSession(identifier + '.1', testRecord).then(function() {
|
||||||
|
return store.getSession(identifier + '.1').then(function(record) {
|
||||||
|
assert.deepEqual(record, testRecord);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
});
|
|
@ -116,7 +116,6 @@
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<script type="text/javascript" src="js/components.js"></script>
|
||||||
<script type="text/javascript" src="js/database.js"></script>
|
<script type="text/javascript" src="js/database.js"></script>
|
||||||
<script type="text/javascript" src="js/libtextsecure.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="js/notifications.js"></script>
|
<script type="text/javascript" src="js/notifications.js"></script>
|
||||||
<script type="text/javascript" src="js/libphonenumber-util.js"></script>
|
<script type="text/javascript" src="js/libphonenumber-util.js"></script>
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<script type="text/javascript" src="js/components.js"></script>
|
||||||
<script type="text/javascript" src="js/database.js"></script>
|
<script type="text/javascript" src="js/database.js"></script>
|
||||||
<script type="text/javascript" src="js/libtextsecure.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="js/libphonenumber-util.js"></script>
|
<script type="text/javascript" src="js/libphonenumber-util.js"></script>
|
||||||
<script type="text/javascript" src="js/models/messages.js"></script>
|
<script type="text/javascript" src="js/models/messages.js"></script>
|
||||||
|
|
|
@ -117,6 +117,7 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/components.js"></script>
|
<script type="text/javascript" src="../js/components.js"></script>
|
||||||
<script type="text/javascript" src="../js/database.js"></script>
|
<script type="text/javascript" src="../js/database.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/axolotl_store.js"></script>
|
||||||
<script type="text/javascript" src="../js/libtextsecure.js"></script>
|
<script type="text/javascript" src="../js/libtextsecure.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/libphonenumber-util.js"></script>
|
<script type="text/javascript" src="../js/libphonenumber-util.js"></script>
|
||||||
|
@ -143,5 +144,6 @@
|
||||||
<script type="text/javascript" src="views/message_list_view_test.js"></script>
|
<script type="text/javascript" src="views/message_list_view_test.js"></script>
|
||||||
<script type="text/javascript" src="models/conversations_test.js"></script>
|
<script type="text/javascript" src="models/conversations_test.js"></script>
|
||||||
<script type="text/javascript" src="models/messages_test.js"></script>
|
<script type="text/javascript" src="models/messages_test.js"></script>
|
||||||
|
<script type="text/javascript" src="storage_test.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
93
test/storage_test.js
Normal file
93
test/storage_test.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/* vim: ts=4:sw=4
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("AxolotlStore", function() {
|
||||||
|
before(function() { localStorage.clear(); });
|
||||||
|
var store = textsecure.storage.axolotl;
|
||||||
|
var identifier = '+5558675309';
|
||||||
|
var identityKey = {
|
||||||
|
pubKey: textsecure.crypto.getRandomBytes(33),
|
||||||
|
privKey: textsecure.crypto.getRandomBytes(32),
|
||||||
|
};
|
||||||
|
var testKey = {
|
||||||
|
pubKey: textsecure.crypto.getRandomBytes(33),
|
||||||
|
privKey: textsecure.crypto.getRandomBytes(32),
|
||||||
|
};
|
||||||
|
it('retrieves my registration id', function() {
|
||||||
|
store.put('registrationId', 1337);
|
||||||
|
var reg = store.getMyRegistrationId();
|
||||||
|
assert.strictEqual(reg, 1337);
|
||||||
|
});
|
||||||
|
it('retrieves my identity key', function() {
|
||||||
|
store.put('identityKey', identityKey);
|
||||||
|
var key = store.getMyIdentityKey();
|
||||||
|
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, identityKey.privKey);
|
||||||
|
});
|
||||||
|
it('stores identity keys', function(done) {
|
||||||
|
store.putIdentityKey(identifier, testKey.pubKey).then(function() {
|
||||||
|
return store.getIdentityKey(identifier).then(function(key) {
|
||||||
|
assertEqualArrayBuffers(key, testKey.pubKey);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('stores prekeys', function(done) {
|
||||||
|
store.putPreKey(1, testKey).then(function() {
|
||||||
|
return store.getPreKey(1).then(function(key) {
|
||||||
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('deletes prekeys', function(done) {
|
||||||
|
before(function(done) {
|
||||||
|
store.putPreKey(2, testKey).then(done);
|
||||||
|
});
|
||||||
|
store.removePreKey(2, testKey).then(function() {
|
||||||
|
return store.getPreKey(2).then(function(key) {
|
||||||
|
assert.isUndefined(key);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('stores signed prekeys', function(done) {
|
||||||
|
store.putSignedPreKey(3, testKey).then(function() {
|
||||||
|
return store.getSignedPreKey(3).then(function(key) {
|
||||||
|
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
|
||||||
|
assertEqualArrayBuffers(key.privKey, testKey.privKey);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('deletes signed prekeys', function(done) {
|
||||||
|
before(function(done) {
|
||||||
|
store.putSignedPreKey(4, testKey).then(done);
|
||||||
|
});
|
||||||
|
store.removeSignedPreKey(4, testKey).then(function() {
|
||||||
|
return store.getSignedPreKey(4).then(function(key) {
|
||||||
|
assert.isUndefined(key);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
it('stores sessions', function(done) {
|
||||||
|
var testRecord = "an opaque string";
|
||||||
|
store.putSession(identifier + '.1', testRecord).then(function() {
|
||||||
|
return store.getSession(identifier + '.1').then(function(record) {
|
||||||
|
assert.deepEqual(record, testRecord);
|
||||||
|
});
|
||||||
|
}).then(done,done);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue