2015-09-07 21:53:43 +00:00
|
|
|
/*
|
|
|
|
* vim: ts=4:sw=4:expandtab
|
2015-04-30 00:00:30 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
;(function () {
|
|
|
|
'use strict';
|
|
|
|
window.textsecure = window.textsecure || {};
|
|
|
|
|
2017-02-16 02:41:32 +00:00
|
|
|
var ARCHIVE_AGE = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
|
Certificate pinning via node XMLHttpRequest implementation (#1394)
* Add certificate pinning on https service requests
Make https requests to the server using node apis instead of browser apis, so we
can specify our own CA list, which contains only our own CA.
This protects us from MITM by a rogue CA.
As a bonus, this let's us drop the use of non-standard ports and just use good
ol' default 443 all the time, at least for http requests.
// FREEBIE
* Make certificateAuthorities an option on requests
Modify node-based xhr implementation based on driverdan/node-XMLHttpRequest,
adding support for setting certificate authorities on each request.
This allows us to pin our master CA for requests to the server and cdn but not
to the s3 attachment server, for instance. Also fix an exception when sending
binary data in a request: it is submitted as an array buffer, and must be
converted to a node Buffer since we are now using a node based request api.
// FREEBIE
* Import node-based xhr implementation
Add a copy of https://github.com/driverdan/node-XMLHttpRequest@86ff70e, and
expose it to the renderer in the preload script.
In later commits this module will be extended to support custom certificate
authorities.
// FREEBIE
* Support "arraybuffer" responseType on requests
When fetching attachments, we want the result as binary data rather than a utf8
string. This lets our node-based XMLHttpRequest honor the responseType property
if it is set on the xhr.
Note that naively using the raw `.buffer` from a node Buffer won't work, since
it is a reuseable backing buffer that is often much larger than the actual
content defined by the Buffer's offset and length.
Instead, we'll prepare a return buffer based on the response's content length
header, and incrementally write chunks of data into it as they arrive.
// FREEBIE
* Switch to self-signed server endpoint
* Log more error info on failed requests
With the node-based xhr, relevant error info are stored in statusText and
responseText when a request fails.
// FREEBIE
* Add node-based websocket w/ support for custom CA
// FREEBIE
* Support handling array buffers instead of blobs
Our node-based websocket calls onmessage with an arraybuffer instead of a blob.
For robustness (on the off chance we switch or update the socket implementation
agian) I've kept the machinery for converting blobs to array buffers.
// FREEBIE
* Destroy all wacky server ports
// FREEBIE
2017-09-01 15:58:58 +00:00
|
|
|
function AccountManager(url, username, password) {
|
|
|
|
this.server = new TextSecureServer(url, username, password);
|
2017-02-14 19:27:59 +00:00
|
|
|
this.pending = Promise.resolve();
|
2015-04-30 00:00:30 +00:00
|
|
|
}
|
|
|
|
|
2018-02-24 01:40:02 +00:00
|
|
|
function getNumber(numberId) {
|
|
|
|
if (!numberId || !numberId.length) {
|
|
|
|
return numberId;
|
|
|
|
}
|
|
|
|
|
|
|
|
var parts = numberId.split('.');
|
|
|
|
if (!parts.length) {
|
|
|
|
return numberId;
|
|
|
|
}
|
|
|
|
|
2018-02-26 19:24:48 +00:00
|
|
|
return parts[0];
|
2018-02-24 01:40:02 +00:00
|
|
|
}
|
|
|
|
|
2016-09-20 20:42:33 +00:00
|
|
|
AccountManager.prototype = new textsecure.EventTarget();
|
|
|
|
AccountManager.prototype.extend({
|
2015-04-30 00:00:30 +00:00
|
|
|
constructor: AccountManager,
|
|
|
|
requestVoiceVerification: function(number) {
|
2015-08-28 22:37:45 +00:00
|
|
|
return this.server.requestVerificationVoice(number);
|
2015-04-30 00:00:30 +00:00
|
|
|
},
|
|
|
|
requestSMSVerification: function(number) {
|
2015-08-28 22:37:45 +00:00
|
|
|
return this.server.requestVerificationSMS(number);
|
2015-04-30 00:00:30 +00:00
|
|
|
},
|
|
|
|
registerSingleDevice: function(number, verificationCode) {
|
2015-08-28 22:37:45 +00:00
|
|
|
var registerKeys = this.server.registerKeys.bind(this.server);
|
|
|
|
var createAccount = this.createAccount.bind(this);
|
2018-01-17 23:28:32 +00:00
|
|
|
var clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
2015-08-28 22:37:45 +00:00
|
|
|
var generateKeys = this.generateKeys.bind(this, 100);
|
2018-01-17 23:28:32 +00:00
|
|
|
var confirmKeys = this.confirmKeys.bind(this);
|
2016-09-20 20:42:33 +00:00
|
|
|
var registrationDone = this.registrationDone.bind(this);
|
2017-02-21 04:11:41 +00:00
|
|
|
return this.queueTask(function() {
|
2017-02-14 19:27:59 +00:00
|
|
|
return libsignal.KeyHelper.generateIdentityKeyPair().then(function(identityKeyPair) {
|
2017-09-11 16:50:35 +00:00
|
|
|
var profileKey = textsecure.crypto.getRandomBytes(32);
|
2018-01-17 23:28:32 +00:00
|
|
|
return createAccount(number, verificationCode, identityKeyPair, profileKey)
|
|
|
|
.then(clearSessionsAndPreKeys)
|
|
|
|
.then(generateKeys)
|
|
|
|
.then(function(keys) {
|
|
|
|
return registerKeys(keys).then(function() {
|
|
|
|
return confirmKeys(keys);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.then(registrationDone);
|
2017-02-21 04:34:09 +00:00
|
|
|
});
|
|
|
|
});
|
2015-04-30 00:00:30 +00:00
|
|
|
},
|
|
|
|
registerSecondDevice: function(setProvisioningUrl, confirmNumber, progressCallback) {
|
2015-08-28 22:37:45 +00:00
|
|
|
var createAccount = this.createAccount.bind(this);
|
2018-01-17 23:28:32 +00:00
|
|
|
var clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
2015-08-28 22:37:45 +00:00
|
|
|
var generateKeys = this.generateKeys.bind(this, 100, progressCallback);
|
2018-01-17 23:28:32 +00:00
|
|
|
var confirmKeys = this.confirmKeys.bind(this);
|
2016-09-20 20:42:33 +00:00
|
|
|
var registrationDone = this.registrationDone.bind(this);
|
2015-08-28 22:37:45 +00:00
|
|
|
var registerKeys = this.server.registerKeys.bind(this.server);
|
2015-08-31 19:18:01 +00:00
|
|
|
var getSocket = this.server.getProvisioningSocket.bind(this.server);
|
2017-02-21 04:31:39 +00:00
|
|
|
var queueTask = this.queueTask.bind(this);
|
|
|
|
var provisioningCipher = new libsignal.ProvisioningCipher();
|
|
|
|
var gotProvisionEnvelope = false;
|
|
|
|
return provisioningCipher.getPublicKey().then(function(pubKey) {
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
var socket = getSocket();
|
|
|
|
socket.onclose = function(e) {
|
2017-04-11 21:43:25 +00:00
|
|
|
console.log('provisioning socket closed', e.code);
|
2017-02-21 04:31:39 +00:00
|
|
|
if (!gotProvisionEnvelope) {
|
2017-02-14 19:27:59 +00:00
|
|
|
reject(new Error('websocket closed'));
|
2017-02-21 04:31:39 +00:00
|
|
|
}
|
|
|
|
};
|
2017-04-11 21:43:25 +00:00
|
|
|
socket.onopen = function(e) {
|
|
|
|
console.log('provisioning socket open');
|
|
|
|
};
|
2017-02-21 04:31:39 +00:00
|
|
|
var wsr = new WebSocketResource(socket, {
|
|
|
|
keepalive: { path: '/v1/keepalive/provisioning' },
|
|
|
|
handleRequest: 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(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');
|
|
|
|
gotProvisionEnvelope = true;
|
|
|
|
wsr.close();
|
|
|
|
resolve(provisioningCipher.decrypt(envelope).then(function(provisionMessage) {
|
|
|
|
return queueTask(function() {
|
2017-02-14 19:27:59 +00:00
|
|
|
return confirmNumber(provisionMessage.number).then(function(deviceName) {
|
|
|
|
if (typeof deviceName !== 'string' || deviceName.length === 0) {
|
|
|
|
throw new Error('Invalid device name');
|
|
|
|
}
|
|
|
|
return createAccount(
|
|
|
|
provisionMessage.number,
|
|
|
|
provisionMessage.provisioningCode,
|
|
|
|
provisionMessage.identityKeyPair,
|
2017-09-11 16:50:35 +00:00
|
|
|
provisionMessage.profileKey,
|
2017-02-14 19:27:59 +00:00
|
|
|
deviceName,
|
Feature: Blue check marks for read messages if opted in (#1489)
* Refactor delivery receipt event handler
* Rename the delivery receipt event
For less ambiguity with read receipts.
* Rename synced read event
For less ambiguity with read receipts from other Signal users.
* Add support for incoming receipt messages
Handle ReceiptMessages, which may include encrypted delivery receipts or read
receipts from recipients of our sent messages.
// FREEBIE
* Rename ReadReceipts to ReadSyncs
* Render read messages with blue double checks
* Send read receipts to senders of incoming messages
// FREEBIE
* Move ReadSyncs to their own file
// FREEBIE
* Fixup old comments on read receipts (now read syncs)
And some variable renaming for extra clarity.
// FREEBIE
* Add global setting for read receipts
Don't send read receipt messages unless the setting is enabled.
Don't process read receipts if the setting is disabled.
// FREEBIE
* Sync read receipt setting from mobile
Toggling this setting on your mobile device should sync it to Desktop. When
linking, use the setting in the provisioning message.
// FREEBIE
* Send receipt messages silently
Avoid generating phantom messages on ios
// FREEBIE
* Save recipients on the outgoing message models
For accurate tracking and display of sent/delivered/read state, even if group
membership changes later.
// FREEBIE
* Fix conversation type in profile key update handling
// FREEBIE
* Set recipients on synced sent messages
* Render saved recipients in message detail if available
For older messages, where we did not save the intended set of recipients at the
time of sending, fall back to the current group membership.
// FREEBIE
* Record who has been successfully sent to
// FREEBIE
* Record who a message has been delivered to
* Invert the not-clickable class
* Fix readReceipt setting sync when linking
* Render per recipient sent/delivered/read status
In the message detail view for outgoing messages, render each recipient's
individual sent/delivered/read status with respect to this message, as long as
there are no errors associated with the recipient (ie, safety number changes,
user not registered, etc...) since the error icon is displayed in that case.
*Messages sent before this change may not have per-recipient status lists
and will simply show no status icon.
// FREEBIE
* Add configuration sync request
Send these requests in a one-off fashion when:
1. We have just setup from a chrome app import
2. We have just upgraded to read-receipt support
// FREEBIE
* Expose sendRequestConfigurationSyncMessage
// FREEBIE
* Fix handling of incoming delivery receipts - union with array
FREEBIE
2017-10-04 22:28:43 +00:00
|
|
|
provisionMessage.userAgent,
|
|
|
|
provisionMessage.readReceipts
|
2018-01-17 23:28:32 +00:00
|
|
|
)
|
|
|
|
.then(clearSessionsAndPreKeys)
|
|
|
|
.then(generateKeys)
|
|
|
|
.then(function(keys) {
|
|
|
|
return registerKeys(keys).then(function() {
|
|
|
|
return confirmKeys(keys);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.then(registrationDone);
|
2017-02-14 19:27:59 +00:00
|
|
|
});
|
2017-02-21 04:31:39 +00:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
console.log('Unknown websocket message', request.path);
|
2015-07-28 21:58:55 +00:00
|
|
|
}
|
2017-02-21 04:31:39 +00:00
|
|
|
}
|
2015-04-30 00:00:30 +00:00
|
|
|
});
|
2017-02-21 04:31:39 +00:00
|
|
|
});
|
|
|
|
});
|
2015-04-30 00:00:30 +00:00
|
|
|
},
|
|
|
|
refreshPreKeys: function() {
|
2015-08-28 22:37:45 +00:00
|
|
|
var generateKeys = this.generateKeys.bind(this, 100);
|
|
|
|
var registerKeys = this.server.registerKeys.bind(this.server);
|
2017-02-14 19:27:59 +00:00
|
|
|
|
2017-02-21 04:11:41 +00:00
|
|
|
return this.queueTask(function() {
|
2017-02-14 19:27:59 +00:00
|
|
|
return this.server.getMyKeys().then(function(preKeyCount) {
|
|
|
|
console.log('prekey count ' + preKeyCount);
|
|
|
|
if (preKeyCount < 10) {
|
|
|
|
return generateKeys().then(registerKeys);
|
|
|
|
}
|
2017-02-21 04:34:09 +00:00
|
|
|
});
|
2015-08-28 22:37:45 +00:00
|
|
|
}.bind(this));
|
|
|
|
},
|
2017-02-16 02:41:32 +00:00
|
|
|
rotateSignedPreKey: function() {
|
2017-02-21 04:11:41 +00:00
|
|
|
return this.queueTask(function() {
|
2017-02-16 02:41:32 +00:00
|
|
|
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
|
|
|
if (typeof signedKeyId != 'number') {
|
|
|
|
throw new Error('Invalid signedKeyId');
|
|
|
|
}
|
2017-12-01 21:35:39 +00:00
|
|
|
|
2017-02-16 02:41:32 +00:00
|
|
|
var store = textsecure.storage.protocol;
|
|
|
|
var server = this.server;
|
|
|
|
var cleanSignedPreKeys = this.cleanSignedPreKeys;
|
2017-12-01 21:35:39 +00:00
|
|
|
|
2018-03-12 21:09:08 +00:00
|
|
|
// TODO: harden this against missing identity key? Otherwise, we get
|
|
|
|
// retries every five seconds.
|
2017-02-16 02:41:32 +00:00
|
|
|
return store.getIdentityKeyPair().then(function(identityKey) {
|
|
|
|
return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
|
2018-03-12 21:09:08 +00:00
|
|
|
}, function(error) {
|
|
|
|
console.log('Failed to get identity key. Canceling key rotation.');
|
2017-02-16 02:41:32 +00:00
|
|
|
}).then(function(res) {
|
2018-03-12 21:09:08 +00:00
|
|
|
if (!res) {
|
|
|
|
return;
|
|
|
|
}
|
2017-12-01 21:35:39 +00:00
|
|
|
console.log('Saving new signed prekey', res.keyId);
|
|
|
|
return Promise.all([
|
|
|
|
textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
|
|
|
store.storeSignedPreKey(res.keyId, res.keyPair),
|
|
|
|
server.setSignedPreKey({
|
|
|
|
keyId : res.keyId,
|
|
|
|
publicKey : res.keyPair.pubKey,
|
|
|
|
signature : res.signature
|
|
|
|
}),
|
|
|
|
]).then(function() {
|
|
|
|
var confirmed = true;
|
|
|
|
console.log('Confirming new signed prekey', res.keyId);
|
|
|
|
return Promise.all([
|
|
|
|
textsecure.storage.remove('signedKeyRotationRejected'),
|
|
|
|
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
|
|
|
|
]);
|
2017-02-16 02:41:32 +00:00
|
|
|
}).then(function() {
|
2017-12-01 21:35:39 +00:00
|
|
|
return cleanSignedPreKeys();
|
2017-02-16 02:41:32 +00:00
|
|
|
});
|
2017-11-30 19:55:59 +00:00
|
|
|
}).catch(function(e) {
|
|
|
|
console.log(
|
|
|
|
'rotateSignedPrekey error:',
|
|
|
|
e && e.stack ? e.stack : e
|
|
|
|
);
|
|
|
|
|
|
|
|
if (e instanceof Error && e.name == 'HTTPError' && e.code >= 400 && e.code <= 599) {
|
|
|
|
var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
|
|
|
|
textsecure.storage.put('signedKeyRotationRejected', rejections);
|
|
|
|
console.log('Signed key rotation rejected count:', rejections);
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
2017-02-16 02:41:32 +00:00
|
|
|
});
|
|
|
|
}.bind(this));
|
2017-02-21 04:11:41 +00:00
|
|
|
},
|
|
|
|
queueTask: function(task) {
|
2017-07-19 19:05:24 +00:00
|
|
|
var taskWithTimeout = textsecure.createTaskWithTimeout(task);
|
|
|
|
return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
|
2017-02-16 02:41:32 +00:00
|
|
|
},
|
|
|
|
cleanSignedPreKeys: function() {
|
2017-12-01 21:35:39 +00:00
|
|
|
var MINIMUM_KEYS = 3;
|
2017-02-16 02:41:32 +00:00
|
|
|
var store = textsecure.storage.protocol;
|
2017-12-01 21:35:39 +00:00
|
|
|
return store.loadSignedPreKeys().then(function(allKeys) {
|
|
|
|
allKeys.sort(function(a, b) {
|
2017-02-16 02:41:32 +00:00
|
|
|
return (a.created_at || 0) - (b.created_at || 0);
|
|
|
|
});
|
2017-12-01 21:35:39 +00:00
|
|
|
allKeys.reverse(); // we want the most recent first
|
|
|
|
var confirmed = allKeys.filter(function(key) {
|
|
|
|
return key.confirmed;
|
|
|
|
});
|
|
|
|
var unconfirmed = allKeys.filter(function(key) {
|
|
|
|
return !key.confirmed;
|
|
|
|
});
|
|
|
|
|
|
|
|
var recent = allKeys[0] ? allKeys[0].keyId : 'none';
|
|
|
|
var recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
|
|
|
console.log('Most recent signed key: ' + recent);
|
|
|
|
console.log('Most recent confirmed signed key: ' + recentConfirmed);
|
|
|
|
console.log(
|
|
|
|
'Total signed key count:',
|
|
|
|
allKeys.length,
|
|
|
|
'-',
|
|
|
|
confirmed.length,
|
|
|
|
'confirmed'
|
|
|
|
);
|
2017-02-16 02:41:32 +00:00
|
|
|
|
2017-12-01 21:35:39 +00:00
|
|
|
var confirmedCount = confirmed.length;
|
2017-02-16 02:41:32 +00:00
|
|
|
|
2017-12-01 21:35:39 +00:00
|
|
|
// Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
|
|
|
|
confirmed = confirmed.forEach(function(key, index) {
|
|
|
|
if (index < MINIMUM_KEYS) {
|
2017-02-16 02:41:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-12-01 21:35:39 +00:00
|
|
|
var created_at = key.created_at || 0;
|
|
|
|
var age = Date.now() - created_at;
|
|
|
|
if (age > ARCHIVE_AGE) {
|
|
|
|
console.log(
|
|
|
|
'Removing confirmed signed prekey:',
|
|
|
|
key.keyId,
|
|
|
|
'with timestamp:',
|
|
|
|
created_at
|
|
|
|
);
|
|
|
|
store.removeSignedPreKey(key.keyId);
|
|
|
|
confirmedCount--;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var stillNeeded = MINIMUM_KEYS - confirmedCount;
|
|
|
|
|
|
|
|
// If we still don't have enough total keys, we keep as many unconfirmed
|
|
|
|
// keys as necessary. If not necessary, and over a week old, we drop.
|
|
|
|
unconfirmed.forEach(function(key, index) {
|
|
|
|
if (index < stillNeeded) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var created_at = key.created_at || 0;
|
|
|
|
var age = Date.now() - created_at;
|
|
|
|
if (age > ARCHIVE_AGE) {
|
|
|
|
console.log(
|
|
|
|
'Removing unconfirmed signed prekey:',
|
|
|
|
key.keyId,
|
|
|
|
'with timestamp:',
|
|
|
|
created_at
|
|
|
|
);
|
|
|
|
store.removeSignedPreKey(key.keyId);
|
2017-02-16 02:41:32 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
Feature: Blue check marks for read messages if opted in (#1489)
* Refactor delivery receipt event handler
* Rename the delivery receipt event
For less ambiguity with read receipts.
* Rename synced read event
For less ambiguity with read receipts from other Signal users.
* Add support for incoming receipt messages
Handle ReceiptMessages, which may include encrypted delivery receipts or read
receipts from recipients of our sent messages.
// FREEBIE
* Rename ReadReceipts to ReadSyncs
* Render read messages with blue double checks
* Send read receipts to senders of incoming messages
// FREEBIE
* Move ReadSyncs to their own file
// FREEBIE
* Fixup old comments on read receipts (now read syncs)
And some variable renaming for extra clarity.
// FREEBIE
* Add global setting for read receipts
Don't send read receipt messages unless the setting is enabled.
Don't process read receipts if the setting is disabled.
// FREEBIE
* Sync read receipt setting from mobile
Toggling this setting on your mobile device should sync it to Desktop. When
linking, use the setting in the provisioning message.
// FREEBIE
* Send receipt messages silently
Avoid generating phantom messages on ios
// FREEBIE
* Save recipients on the outgoing message models
For accurate tracking and display of sent/delivered/read state, even if group
membership changes later.
// FREEBIE
* Fix conversation type in profile key update handling
// FREEBIE
* Set recipients on synced sent messages
* Render saved recipients in message detail if available
For older messages, where we did not save the intended set of recipients at the
time of sending, fall back to the current group membership.
// FREEBIE
* Record who has been successfully sent to
// FREEBIE
* Record who a message has been delivered to
* Invert the not-clickable class
* Fix readReceipt setting sync when linking
* Render per recipient sent/delivered/read status
In the message detail view for outgoing messages, render each recipient's
individual sent/delivered/read status with respect to this message, as long as
there are no errors associated with the recipient (ie, safety number changes,
user not registered, etc...) since the error icon is displayed in that case.
*Messages sent before this change may not have per-recipient status lists
and will simply show no status icon.
// FREEBIE
* Add configuration sync request
Send these requests in a one-off fashion when:
1. We have just setup from a chrome app import
2. We have just upgraded to read-receipt support
// FREEBIE
* Expose sendRequestConfigurationSyncMessage
// FREEBIE
* Fix handling of incoming delivery receipts - union with array
FREEBIE
2017-10-04 22:28:43 +00:00
|
|
|
createAccount: function(number, verificationCode, identityKeyPair,
|
|
|
|
profileKey, deviceName, userAgent, readReceipts) {
|
2016-05-08 00:49:45 +00:00
|
|
|
var signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
|
|
|
var password = btoa(getString(libsignal.crypto.getRandomBytes(16)));
|
2015-08-28 22:37:45 +00:00
|
|
|
password = password.substring(0, password.length - 2);
|
2016-04-28 22:07:34 +00:00
|
|
|
var registrationId = libsignal.KeyHelper.generateRegistrationId();
|
2015-04-30 00:00:30 +00:00
|
|
|
|
2018-02-24 01:40:02 +00:00
|
|
|
var previousNumber = getNumber(textsecure.storage.get('number_id'));
|
|
|
|
|
2015-08-28 22:37:45 +00:00
|
|
|
return this.server.confirmCode(
|
|
|
|
number, verificationCode, password, signalingKey, registrationId, deviceName
|
|
|
|
).then(function(response) {
|
2018-02-24 01:40:02 +00:00
|
|
|
if (previousNumber && previousNumber !== number) {
|
|
|
|
console.log('New number is different from old number; deleting all previous data');
|
|
|
|
|
|
|
|
return textsecure.storage.protocol.removeAllData().then(function() {
|
|
|
|
console.log('Successfully deleted previous data');
|
|
|
|
return response;
|
|
|
|
}, function(error) {
|
|
|
|
console.log(
|
|
|
|
'Something went wrong deleting data from previous number',
|
|
|
|
error && error.stack ? error.stack : error
|
|
|
|
);
|
|
|
|
|
|
|
|
return response;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}).then(function(response) {
|
2018-01-17 23:28:32 +00:00
|
|
|
textsecure.storage.remove('identityKey');
|
|
|
|
textsecure.storage.remove('signaling_key');
|
|
|
|
textsecure.storage.remove('password');
|
|
|
|
textsecure.storage.remove('registrationId');
|
|
|
|
textsecure.storage.remove('number_id');
|
|
|
|
textsecure.storage.remove('device_name');
|
|
|
|
textsecure.storage.remove('regionCode');
|
|
|
|
textsecure.storage.remove('userAgent');
|
|
|
|
textsecure.storage.remove('profileKey');
|
|
|
|
textsecure.storage.remove('read-receipts-setting');
|
2015-11-25 00:14:33 +00:00
|
|
|
|
2018-01-17 23:28:32 +00:00
|
|
|
// update our own identity key, which may have changed
|
|
|
|
// if we're relinking after a reinstall on the master device
|
|
|
|
textsecure.storage.protocol.saveIdentityWithAttributes(number, {
|
|
|
|
id : number,
|
|
|
|
publicKey : identityKeyPair.pubKey,
|
|
|
|
firstUse : true,
|
|
|
|
timestamp : Date.now(),
|
|
|
|
verified : textsecure.storage.protocol.VerifiedStatus.VERIFIED,
|
|
|
|
nonblockingApproval : true
|
|
|
|
});
|
Feature: Blue check marks for read messages if opted in (#1489)
* Refactor delivery receipt event handler
* Rename the delivery receipt event
For less ambiguity with read receipts.
* Rename synced read event
For less ambiguity with read receipts from other Signal users.
* Add support for incoming receipt messages
Handle ReceiptMessages, which may include encrypted delivery receipts or read
receipts from recipients of our sent messages.
// FREEBIE
* Rename ReadReceipts to ReadSyncs
* Render read messages with blue double checks
* Send read receipts to senders of incoming messages
// FREEBIE
* Move ReadSyncs to their own file
// FREEBIE
* Fixup old comments on read receipts (now read syncs)
And some variable renaming for extra clarity.
// FREEBIE
* Add global setting for read receipts
Don't send read receipt messages unless the setting is enabled.
Don't process read receipts if the setting is disabled.
// FREEBIE
* Sync read receipt setting from mobile
Toggling this setting on your mobile device should sync it to Desktop. When
linking, use the setting in the provisioning message.
// FREEBIE
* Send receipt messages silently
Avoid generating phantom messages on ios
// FREEBIE
* Save recipients on the outgoing message models
For accurate tracking and display of sent/delivered/read state, even if group
membership changes later.
// FREEBIE
* Fix conversation type in profile key update handling
// FREEBIE
* Set recipients on synced sent messages
* Render saved recipients in message detail if available
For older messages, where we did not save the intended set of recipients at the
time of sending, fall back to the current group membership.
// FREEBIE
* Record who has been successfully sent to
// FREEBIE
* Record who a message has been delivered to
* Invert the not-clickable class
* Fix readReceipt setting sync when linking
* Render per recipient sent/delivered/read status
In the message detail view for outgoing messages, render each recipient's
individual sent/delivered/read status with respect to this message, as long as
there are no errors associated with the recipient (ie, safety number changes,
user not registered, etc...) since the error icon is displayed in that case.
*Messages sent before this change may not have per-recipient status lists
and will simply show no status icon.
// FREEBIE
* Add configuration sync request
Send these requests in a one-off fashion when:
1. We have just setup from a chrome app import
2. We have just upgraded to read-receipt support
// FREEBIE
* Expose sendRequestConfigurationSyncMessage
// FREEBIE
* Fix handling of incoming delivery receipts - union with array
FREEBIE
2017-10-04 22:28:43 +00:00
|
|
|
|
2018-01-17 23:28:32 +00:00
|
|
|
textsecure.storage.put('identityKey', identityKeyPair);
|
|
|
|
textsecure.storage.put('signaling_key', signalingKey);
|
|
|
|
textsecure.storage.put('password', password);
|
|
|
|
textsecure.storage.put('registrationId', registrationId);
|
|
|
|
if (profileKey) {
|
|
|
|
textsecure.storage.put('profileKey', profileKey);
|
|
|
|
}
|
|
|
|
if (userAgent) {
|
|
|
|
textsecure.storage.put('userAgent', userAgent);
|
|
|
|
}
|
|
|
|
if (readReceipts) {
|
|
|
|
textsecure.storage.put('read-receipt-setting', true);
|
|
|
|
} else {
|
|
|
|
textsecure.storage.put('read-receipt-setting', false);
|
|
|
|
}
|
2015-06-23 21:24:13 +00:00
|
|
|
|
2018-01-17 23:28:32 +00:00
|
|
|
textsecure.storage.user.setNumberAndDeviceId(number, response.deviceId || 1, deviceName);
|
|
|
|
textsecure.storage.put('regionCode', libphonenumber.util.getRegionCodeForNumber(number));
|
|
|
|
this.server.username = textsecure.storage.get('number_id');
|
2015-10-02 03:34:36 +00:00
|
|
|
}.bind(this));
|
2015-08-28 22:37:45 +00:00
|
|
|
},
|
2018-01-17 23:28:32 +00:00
|
|
|
clearSessionsAndPreKeys: function() {
|
|
|
|
var store = textsecure.storage.protocol;
|
|
|
|
|
|
|
|
console.log('clearing all sessions, prekeys, and signed prekeys');
|
|
|
|
return Promise.all([
|
|
|
|
store.clearPreKeyStore(),
|
|
|
|
store.clearSignedPreKeysStore(),
|
|
|
|
store.clearSessionStore(),
|
|
|
|
]);
|
|
|
|
},
|
|
|
|
// Takes the same object returned by generateKeys
|
|
|
|
confirmKeys: function(keys) {
|
|
|
|
var store = textsecure.storage.protocol;
|
|
|
|
var key = keys.signedPreKey;
|
|
|
|
var confirmed = true;
|
|
|
|
|
|
|
|
console.log('confirmKeys: confirming key', key.keyId);
|
|
|
|
return store.storeSignedPreKey(key.keyId, key.keyPair, confirmed);
|
|
|
|
},
|
2015-08-28 22:37:45 +00:00
|
|
|
generateKeys: function (count, progressCallback) {
|
|
|
|
if (typeof progressCallback !== 'function') {
|
|
|
|
progressCallback = undefined;
|
|
|
|
}
|
|
|
|
var startId = textsecure.storage.get('maxPreKeyId', 1);
|
|
|
|
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
|
2015-04-30 00:00:30 +00:00
|
|
|
|
2015-08-28 22:37:45 +00:00
|
|
|
if (typeof startId != 'number') {
|
|
|
|
throw new Error('Invalid maxPreKeyId');
|
|
|
|
}
|
|
|
|
if (typeof signedKeyId != 'number') {
|
|
|
|
throw new Error('Invalid signedKeyId');
|
|
|
|
}
|
2015-04-30 00:00:30 +00:00
|
|
|
|
2016-04-22 20:39:05 +00:00
|
|
|
var store = textsecure.storage.protocol;
|
2016-04-21 22:40:43 +00:00
|
|
|
return store.getIdentityKeyPair().then(function(identityKey) {
|
2015-08-28 22:37:45 +00:00
|
|
|
var result = { preKeys: [], identityKey: identityKey.pubKey };
|
|
|
|
var promises = [];
|
2015-04-30 00:00:30 +00:00
|
|
|
|
2015-08-28 22:37:45 +00:00
|
|
|
for (var keyId = startId; keyId < startId+count; ++keyId) {
|
|
|
|
promises.push(
|
2016-04-28 22:07:34 +00:00
|
|
|
libsignal.KeyHelper.generatePreKey(keyId).then(function(res) {
|
2016-04-21 22:40:43 +00:00
|
|
|
store.storePreKey(res.keyId, res.keyPair);
|
2015-08-28 22:37:45 +00:00
|
|
|
result.preKeys.push({
|
|
|
|
keyId : res.keyId,
|
|
|
|
publicKey : res.keyPair.pubKey
|
|
|
|
});
|
|
|
|
if (progressCallback) { progressCallback(); }
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2015-04-30 00:00:30 +00:00
|
|
|
|
2015-08-28 22:37:45 +00:00
|
|
|
promises.push(
|
2016-04-28 22:07:34 +00:00
|
|
|
libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId).then(function(res) {
|
2016-04-21 22:40:43 +00:00
|
|
|
store.storeSignedPreKey(res.keyId, res.keyPair);
|
2015-08-28 22:37:45 +00:00
|
|
|
result.signedPreKey = {
|
|
|
|
keyId : res.keyId,
|
|
|
|
publicKey : res.keyPair.pubKey,
|
2018-01-17 23:28:32 +00:00
|
|
|
signature : res.signature,
|
|
|
|
// server.registerKeys doesn't use keyPair, confirmKeys does
|
|
|
|
keyPair : res.keyPair,
|
2015-08-28 22:37:45 +00:00
|
|
|
};
|
|
|
|
})
|
|
|
|
);
|
2015-05-11 22:40:21 +00:00
|
|
|
|
2015-08-28 22:37:45 +00:00
|
|
|
textsecure.storage.put('maxPreKeyId', startId + count);
|
|
|
|
textsecure.storage.put('signedKeyId', signedKeyId + 1);
|
|
|
|
return Promise.all(promises).then(function() {
|
2018-01-17 23:28:32 +00:00
|
|
|
// This is primarily for the signed prekey summary it logs out
|
2017-02-16 02:41:32 +00:00
|
|
|
return this.cleanSignedPreKeys().then(function() {
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
}.bind(this));
|
|
|
|
}.bind(this));
|
2016-09-20 20:42:33 +00:00
|
|
|
},
|
|
|
|
registrationDone: function() {
|
2017-02-04 05:21:18 +00:00
|
|
|
console.log('registration done');
|
2016-09-20 20:42:33 +00:00
|
|
|
this.dispatchEvent(new Event('registration'));
|
2015-05-11 22:40:21 +00:00
|
|
|
}
|
2016-09-20 20:42:33 +00:00
|
|
|
});
|
2015-08-28 22:37:45 +00:00
|
|
|
textsecure.AccountManager = AccountManager;
|
2015-04-30 00:00:30 +00:00
|
|
|
|
2015-08-28 22:37:45 +00:00
|
|
|
}());
|