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',
|
||||
},
|
||||
key_worker: {
|
||||
options: {
|
||||
banner: 'var window = this;\n',
|
||||
},
|
||||
src: [
|
||||
'js/libtextsecure.js',
|
||||
'libtextsecure/key_worker.js'
|
||||
],
|
||||
dest: 'js/key_worker.js'
|
||||
},
|
||||
libtextsecuretest: {
|
||||
src: [
|
||||
'components/mock-socket/dist/mock-socket.js',
|
||||
|
@ -148,7 +138,7 @@ module.exports = function(grunt) {
|
|||
},
|
||||
jscs: {
|
||||
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: {
|
||||
|
@ -164,10 +154,6 @@ module.exports = function(grunt) {
|
|||
files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
|
||||
tasks: ['concat:libtextsecure']
|
||||
},
|
||||
key_worker: {
|
||||
files: ['<%= concat.key_worker.src %>'],
|
||||
tasks: ['concat:key_worker']
|
||||
},
|
||||
dist: {
|
||||
files: ['<%= dist.src %>'],
|
||||
tasks: ['copy']
|
||||
|
|
|
@ -230,6 +230,7 @@
|
|||
</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/axolotl_store.js"></script>
|
||||
<script type="text/javascript" src="js/libtextsecure.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("group", "members", { unique: false, multiEntry: true });
|
||||
conversations.createIndex("type", "type", { unique: false });
|
||||
|
||||
var preKeys = transaction.db.createObjectStore("preKeys");
|
||||
var signedPreKeys = transaction.db.createObjectStore("signedPreKeys");
|
||||
|
||||
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() {
|
||||
var axolotlInstance = axolotl.protocol({
|
||||
getMyRegistrationId: function() {
|
||||
return textsecure.storage.get("registrationId");
|
||||
},
|
||||
put: function(key, value) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.storage = window.textsecure.storage || {};
|
||||
textsecure.storage.axolotl = new AxolotlStore();
|
||||
var axolotlInstance = axolotl.protocol(textsecure.storage.axolotl);
|
||||
|
||||
var decodeMessageContents = function(res) {
|
||||
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
|
||||
|
@ -37826,7 +37791,7 @@ axolotlInternal.RecipientRecord = function() {
|
|||
|
||||
var handlePreKeyWhisperMessage = function(from, message) {
|
||||
try {
|
||||
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, message);
|
||||
return axolotlInstance.handlePreKeyWhisperMessage(from, message);
|
||||
} catch(e) {
|
||||
if (e.message === 'Unknown identity key') {
|
||||
// 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));
|
||||
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
|
||||
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:
|
||||
if (proto.message.readUint8() != ((3 << 4) | 3))
|
||||
throw new Error("Bad version byte");
|
||||
|
@ -37860,27 +37825,34 @@ axolotlInternal.RecipientRecord = function() {
|
|||
closeOpenSessionForDevice: function(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) {
|
||||
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
|
||||
},
|
||||
generateKeys: function() {
|
||||
return axolotlInstance.generateKeys();
|
||||
generateKeys: function(count, progressCallback) {
|
||||
if (textsecure.worker_path) {
|
||||
axolotlInstance.startWorker(textsecure.worker_path);
|
||||
}
|
||||
return generateKeys(count, progressCallback).then(function(result) {
|
||||
axolotlInstance.stopWorker();
|
||||
return result;
|
||||
});
|
||||
},
|
||||
createIdentityKeyRecvSocket: function() {
|
||||
return axolotlInstance.createIdentityKeyRecvSocket();
|
||||
},
|
||||
hasOpenSession: function(encodedNumber) {
|
||||
return axolotlInstance.hasOpenSession(encodedNumber);
|
||||
},
|
||||
getRegistrationId: function(encodedNumber) {
|
||||
return axolotlInstance.getRegistrationId(encodedNumber);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
})();
|
||||
|
||||
/* vim: ts=4:sw=4:expandtab
|
||||
|
@ -38136,46 +38108,30 @@ axolotlInternal.RecipientRecord = function() {
|
|||
if (sessions[deviceId] === undefined)
|
||||
return undefined;
|
||||
|
||||
var record = new axolotl.sessions.RecipientRecord();
|
||||
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;
|
||||
return sessions[deviceId];
|
||||
},
|
||||
|
||||
putSessionsForDevice: function(encodedNumber, record) {
|
||||
var number = textsecure.utils.unencodeNumber(encodedNumber)[0];
|
||||
var deviceId = textsecure.utils.unencodeNumber(encodedNumber)[1];
|
||||
|
||||
textsecure.storage.devices.checkSaveIdentityKeyForNumber(number, record.identityKey);
|
||||
|
||||
var sessions = textsecure.storage.get("sessions" + number);
|
||||
if (sessions === undefined)
|
||||
sessions = {};
|
||||
sessions[deviceId] = record.serialize();
|
||||
sessions[deviceId] = record;
|
||||
textsecure.storage.put("sessions" + number, sessions);
|
||||
|
||||
var device = textsecure.storage.devices.getDeviceObject(encodedNumber);
|
||||
if (device === undefined) {
|
||||
var identityKey = textsecure.storage.devices.getIdentityKeyForNumber(number);
|
||||
device = { encodedNumber: encodedNumber,
|
||||
//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);
|
||||
},
|
||||
|
||||
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
|
||||
_removeIdentityKeyForNumber: function(number) {
|
||||
textsecure.storage.remove("sessions" + number);
|
||||
|
@ -38820,6 +38776,8 @@ window.textsecure.utils = function() {
|
|||
for (var key in thing)
|
||||
res[key] = ensureStringed(thing[key]);
|
||||
return res;
|
||||
} else if (thing === null) {
|
||||
return null;
|
||||
}
|
||||
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 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);
|
||||
textsecure.storage.put('signaling_key', signalingKey);
|
||||
|
||||
|
@ -38963,39 +38923,95 @@ window.textsecure.registerSingleDevice = function(number, verificationCode, step
|
|||
password = password.substring(0, password.length - 2);
|
||||
textsecure.storage.put("password", password);
|
||||
|
||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
||||
registrationId = registrationId & 0x3fff;
|
||||
var registrationId = axolotl.util.generateRegistrationId();
|
||||
textsecure.storage.put("registrationId", registrationId);
|
||||
|
||||
return textsecure.api.confirmCode(number, verificationCode, password, signalingKey, registrationId, true).then(function() {
|
||||
textsecure.storage.user.setNumberAndDeviceId(number, 1);
|
||||
return textsecure.api.confirmCode(
|
||||
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));
|
||||
stepDone(1);
|
||||
|
||||
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
|
||||
stepDone(2);
|
||||
return textsecure.api.registerKeys(keys).then(function() {
|
||||
stepDone(3);
|
||||
});
|
||||
});
|
||||
return textsecure.protocol_wrapper.generateKeys().then(textsecure.registration.done);
|
||||
});
|
||||
}
|
||||
|
||||
window.textsecure.registerSecondDevice = function(provisionMessage) {
|
||||
var signalingKey = textsecure.crypto.getRandomBytes(32 + 20);
|
||||
textsecure.storage.put('signaling_key', signalingKey);
|
||||
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 password = btoa(getString(textsecure.crypto.getRandomBytes(16)));
|
||||
password = password.substring(0, password.length - 2);
|
||||
textsecure.storage.put("password", password);
|
||||
var startId = textsecure.storage.get('maxPreKeyId', 1);
|
||||
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
||||
|
||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
||||
registrationId = registrationId & 0x3fff;
|
||||
textsecure.storage.put("registrationId", registrationId);
|
||||
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
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
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));
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -39584,22 +39600,21 @@ window.textsecure.messaging = function() {
|
|||
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) {
|
||||
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] = {
|
||||
type: encryptedMsg.type,
|
||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||
destinationRegistrationId: registrationId,
|
||||
body: encryptedMsg.body,
|
||||
timestamp: timestamp
|
||||
};
|
||||
jsonData[i] = {
|
||||
type: encryptedMsg.type,
|
||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||
destinationRegistrationId: registrationId,
|
||||
body: encryptedMsg.body,
|
||||
timestamp: timestamp
|
||||
};
|
||||
|
||||
if (deviceObjectList[i].relay !== undefined)
|
||||
jsonData[i].relay = deviceObjectList[i].relay;
|
||||
if (deviceObjectList[i].relay !== undefined)
|
||||
jsonData[i].relay = deviceObjectList[i].relay;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -39615,37 +39630,37 @@ window.textsecure.messaging = function() {
|
|||
groupId = getString(groupId);
|
||||
|
||||
var doUpdate = false;
|
||||
for (var i in devicesForNumber) {
|
||||
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;
|
||||
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);
|
||||
Promise.all(devicesForNumber.map(function(device) {
|
||||
return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
|
||||
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId))
|
||||
doUpdate = true;
|
||||
});
|
||||
} else {
|
||||
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
||||
}
|
||||
})).then(function() {
|
||||
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) {
|
||||
|
@ -39730,7 +39745,7 @@ window.textsecure.messaging = function() {
|
|||
|
||||
var promises = [];
|
||||
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])]);
|
||||
|
||||
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').hide();
|
||||
});
|
||||
|
||||
$(function() {
|
||||
if (textsecure.registration.isDone()) {
|
||||
$('#complete-number').text(textsecure.storage.user.getNumber());
|
||||
var bg = extension.windows.getBackground();
|
||||
if (bg.textsecure.registration.isDone()) {
|
||||
$('#complete-number').text(bg.textsecure.storage.user.getNumber());
|
||||
$('#setup-complete').show().addClass('in');
|
||||
initOptions();
|
||||
} else {
|
||||
$('#init-setup').show().addClass('in');
|
||||
$('#status').text("Connecting...");
|
||||
textsecure.protocol_wrapper.createIdentityKeyRecvSocket().then(function(cryptoInfo) {
|
||||
var qrCode = new QRCode(document.getElementById('qr'));
|
||||
var socket = textsecure.api.getTempWebsocket();
|
||||
new WebSocketResource(socket, function(request) {
|
||||
if (request.path == "/v1/address" && request.verb == "PUT") {
|
||||
var proto = textsecure.protobuf.ProvisioningUuid.decode(request.body);
|
||||
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);
|
||||
});
|
||||
|
||||
bg.textsecure.registerSecondDevice(setProvisioningUrl, confirmNumber, incrementCounter).then(function() {
|
||||
$('.modal-container').hide();
|
||||
$('#init-setup').hide();
|
||||
$('#setup-complete').show().addClass('in');
|
||||
initOptions();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
;(function() {
|
||||
'use strict';
|
||||
var bg = extension.windows.getBackground();
|
||||
|
||||
function log(s) {
|
||||
console.log(s);
|
||||
|
@ -36,7 +37,7 @@
|
|||
var phoneView = new Whisper.PhoneInputView({el: $('#phone-number-input')});
|
||||
phoneView.$el.find('input.number').intlTelInput();
|
||||
|
||||
var number = textsecure.storage.user.getNumber();
|
||||
var number = bg.textsecure.storage.user.getNumber();
|
||||
if (number) {
|
||||
$('input.number').val(number);
|
||||
}
|
||||
|
@ -60,7 +61,7 @@
|
|||
$('#error').hide();
|
||||
var number = phoneView.validateNumber();
|
||||
if (number) {
|
||||
textsecure.api.requestVerificationVoice(number).catch(displayError);
|
||||
bg.textsecure.api.requestVerificationVoice(number).catch(displayError);
|
||||
$('#step2').addClass('in').fadeIn();
|
||||
} else {
|
||||
$('#number-container').addClass('invalid');
|
||||
|
@ -71,7 +72,7 @@
|
|||
$('#error').hide();
|
||||
var number = phoneView.validateNumber();
|
||||
if (number) {
|
||||
textsecure.api.requestVerificationSMS(number).catch(displayError);
|
||||
bg.textsecure.api.requestVerificationSMS(number).catch(displayError);
|
||||
$('#step2').addClass('in').fadeIn();
|
||||
} else {
|
||||
$('#number-container').addClass('invalid');
|
||||
|
@ -80,40 +81,14 @@
|
|||
|
||||
$('#form').submit(function(e) {
|
||||
e.preventDefault();
|
||||
log('registering');
|
||||
var number = phoneView.validateNumber();
|
||||
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.setItem('first_install_ran', 1);
|
||||
textsecure.storage.put('registrationId', registrationId);
|
||||
textsecure.storage.put('signaling_key', signalingKey);
|
||||
textsecure.storage.put('password', password);
|
||||
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();
|
||||
});
|
||||
});
|
||||
bg.textsecure.registerSingleDevice(number, verificationCode).then(function() {
|
||||
extension.navigator.tabs.create("options.html");
|
||||
window.close();
|
||||
}).catch(function(e) {
|
||||
log(e);
|
||||
});
|
||||
|
|
|
@ -1,44 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
;(function() {
|
||||
var axolotlInstance = axolotl.protocol({
|
||||
getMyRegistrationId: function() {
|
||||
return textsecure.storage.get("registrationId");
|
||||
},
|
||||
put: function(key, value) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
'use strict';
|
||||
window.textsecure = window.textsecure || {};
|
||||
window.textsecure.storage = window.textsecure.storage || {};
|
||||
textsecure.storage.axolotl = new AxolotlStore();
|
||||
var axolotlInstance = axolotl.protocol(textsecure.storage.axolotl);
|
||||
|
||||
var decodeMessageContents = function(res) {
|
||||
var finalMessage = textsecure.protobuf.PushMessageContent.decode(res[0]);
|
||||
|
@ -53,7 +18,7 @@
|
|||
|
||||
var handlePreKeyWhisperMessage = function(from, message) {
|
||||
try {
|
||||
return textsecure.protocol_wrapper.handlePreKeyWhisperMessage(from, message);
|
||||
return axolotlInstance.handlePreKeyWhisperMessage(from, message);
|
||||
} catch(e) {
|
||||
if (e.message === 'Unknown identity key') {
|
||||
// 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));
|
||||
case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT:
|
||||
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:
|
||||
if (proto.message.readUint8() != ((3 << 4) | 3))
|
||||
throw new Error("Bad version byte");
|
||||
|
@ -87,25 +52,32 @@
|
|||
closeOpenSessionForDevice: function(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) {
|
||||
return axolotlInstance.encryptMessageFor(deviceObject, pushMessageContent);
|
||||
},
|
||||
generateKeys: function() {
|
||||
return axolotlInstance.generateKeys();
|
||||
generateKeys: function(count, progressCallback) {
|
||||
if (textsecure.worker_path) {
|
||||
axolotlInstance.startWorker(textsecure.worker_path);
|
||||
}
|
||||
return generateKeys(count, progressCallback).then(function(result) {
|
||||
axolotlInstance.stopWorker();
|
||||
return result;
|
||||
});
|
||||
},
|
||||
createIdentityKeyRecvSocket: function() {
|
||||
return axolotlInstance.createIdentityKeyRecvSocket();
|
||||
},
|
||||
hasOpenSession: function(encodedNumber) {
|
||||
return axolotlInstance.hasOpenSession(encodedNumber);
|
||||
},
|
||||
getRegistrationId: function(encodedNumber) {
|
||||
return axolotlInstance.getRegistrationId(encodedNumber);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
})();
|
||||
|
|
|
@ -111,6 +111,8 @@ window.textsecure.utils = function() {
|
|||
for (var key in thing)
|
||||
res[key] = ensureStringed(thing[key]);
|
||||
return res;
|
||||
} else if (thing === null) {
|
||||
return null;
|
||||
}
|
||||
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 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);
|
||||
textsecure.storage.put('signaling_key', signalingKey);
|
||||
|
||||
|
@ -254,38 +258,94 @@ window.textsecure.registerSingleDevice = function(number, verificationCode, step
|
|||
password = password.substring(0, password.length - 2);
|
||||
textsecure.storage.put("password", password);
|
||||
|
||||
var registrationId = new Uint16Array(textsecure.crypto.getRandomBytes(2))[0];
|
||||
registrationId = registrationId & 0x3fff;
|
||||
var registrationId = axolotl.util.generateRegistrationId();
|
||||
textsecure.storage.put("registrationId", registrationId);
|
||||
|
||||
return textsecure.api.confirmCode(number, verificationCode, password, signalingKey, registrationId, true).then(function() {
|
||||
textsecure.storage.user.setNumberAndDeviceId(number, 1);
|
||||
return textsecure.api.confirmCode(
|
||||
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));
|
||||
stepDone(1);
|
||||
|
||||
return textsecure.protocol_wrapper.generateKeys().then(function(keys) {
|
||||
stepDone(2);
|
||||
return textsecure.api.registerKeys(keys).then(function() {
|
||||
stepDone(3);
|
||||
return textsecure.protocol_wrapper.generateKeys().then(textsecure.registration.done);
|
||||
});
|
||||
}
|
||||
|
||||
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); });
|
||||
}
|
||||
|
||||
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) {
|
||||
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] = {
|
||||
type: encryptedMsg.type,
|
||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||
destinationRegistrationId: registrationId,
|
||||
body: encryptedMsg.body,
|
||||
timestamp: timestamp
|
||||
};
|
||||
jsonData[i] = {
|
||||
type: encryptedMsg.type,
|
||||
destinationDeviceId: textsecure.utils.unencodeNumber(deviceObjectList[i].encodedNumber)[1],
|
||||
destinationRegistrationId: registrationId,
|
||||
body: encryptedMsg.body,
|
||||
timestamp: timestamp
|
||||
};
|
||||
|
||||
if (deviceObjectList[i].relay !== undefined)
|
||||
jsonData[i].relay = deviceObjectList[i].relay;
|
||||
if (deviceObjectList[i].relay !== undefined)
|
||||
jsonData[i].relay = deviceObjectList[i].relay;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -98,37 +97,37 @@ window.textsecure.messaging = function() {
|
|||
groupId = getString(groupId);
|
||||
|
||||
var doUpdate = false;
|
||||
for (var i in devicesForNumber) {
|
||||
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;
|
||||
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);
|
||||
Promise.all(devicesForNumber.map(function(device) {
|
||||
return textsecure.protocol_wrapper.getRegistrationId(device.encodedNumber).then(function(registrationId) {
|
||||
if (textsecure.storage.groups.needUpdateByDeviceRegistrationId(groupId, number, devicesForNumber[i].encodedNumber, registrationId))
|
||||
doUpdate = true;
|
||||
});
|
||||
} else {
|
||||
return sendMessageToDevices(Date.now(), number, devicesForNumber, proto);
|
||||
}
|
||||
})).then(function() {
|
||||
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) {
|
||||
|
@ -213,7 +212,7 @@ window.textsecure.messaging = function() {
|
|||
|
||||
var promises = [];
|
||||
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])]);
|
||||
|
||||
Promise.all(promises).then(function() {
|
||||
|
|
|
@ -34,46 +34,30 @@
|
|||
if (sessions[deviceId] === undefined)
|
||||
return undefined;
|
||||
|
||||
var record = new axolotl.sessions.RecipientRecord();
|
||||
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;
|
||||
return sessions[deviceId];
|
||||
},
|
||||
|
||||
putSessionsForDevice: function(encodedNumber, record) {
|
||||
var number = textsecure.utils.unencodeNumber(encodedNumber)[0];
|
||||
var deviceId = textsecure.utils.unencodeNumber(encodedNumber)[1];
|
||||
|
||||
textsecure.storage.devices.checkSaveIdentityKeyForNumber(number, record.identityKey);
|
||||
|
||||
var sessions = textsecure.storage.get("sessions" + number);
|
||||
if (sessions === undefined)
|
||||
sessions = {};
|
||||
sessions[deviceId] = record.serialize();
|
||||
sessions[deviceId] = record;
|
||||
textsecure.storage.put("sessions" + number, sessions);
|
||||
|
||||
var device = textsecure.storage.devices.getDeviceObject(encodedNumber);
|
||||
if (device === undefined) {
|
||||
var identityKey = textsecure.storage.devices.getIdentityKeyForNumber(number);
|
||||
device = { encodedNumber: encodedNumber,
|
||||
//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);
|
||||
},
|
||||
|
||||
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
|
||||
_removeIdentityKeyForNumber: function(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="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="../crypto.js"></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="../storage.js" data-cover></script>
|
||||
|
@ -49,6 +50,8 @@
|
|||
<script type="text/javascript" src="helpers_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="storage_test.js"></script>
|
||||
<script type="text/javascript" src="generate_keys_test.js"></script>
|
||||
<script type="text/javascript" src="websocket_test.js"></script>
|
||||
</body>
|
||||
</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>
|
||||
<script type="text/javascript" src="js/components.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/libphonenumber-util.js"></script>
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
</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/libtextsecure.js"></script>
|
||||
|
||||
<script type="text/javascript" src="js/libphonenumber-util.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/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/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="models/conversations_test.js"></script>
|
||||
<script type="text/javascript" src="models/messages_test.js"></script>
|
||||
<script type="text/javascript" src="storage_test.js"></script>
|
||||
</body>
|
||||
</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