Merge branch 'master' into development

Bringing beta up to date with production v1.0.40
This commit is contained in:
Scott Nonnenberg 2017-12-04 16:08:19 -08:00
commit f013eed9d1
No known key found for this signature in database
GPG key ID: A4931C09644C654B
19 changed files with 675 additions and 251 deletions

View file

@ -88,9 +88,10 @@
function start() {
var currentVersion = window.config.version;
var lastVersion = storage.get('version');
var newVersion = !lastVersion || currentVersion !== lastVersion;
storage.put('version', currentVersion);
if (!lastVersion || currentVersion !== lastVersion) {
if (newVersion) {
console.log('New version detected:', currentVersion);
}
@ -99,7 +100,7 @@
console.log('listening for registration events');
Whisper.events.on('registration_done', function() {
console.log('handling registration event');
Whisper.RotateSignedPreKeyListener.init(Whisper.events);
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
connect(true);
});
@ -112,7 +113,7 @@
console.log('Import was interrupted, showing import error screen');
appView.openImporter();
} else if (Whisper.Registration.everDone()) {
Whisper.RotateSignedPreKeyListener.init(Whisper.events);
Whisper.RotateSignedPreKeyListener.init(Whisper.events, newVersion);
connect();
appView.openInbox({
initialLoadComplete: initialLoadComplete
@ -377,6 +378,14 @@
return ConversationController.getOrCreateAndWait(id, 'private')
.then(function(conversation) {
return new Promise(function(resolve, reject) {
var activeAt = conversation.get('active_at');
// The idea is to make any new contact show up in the left pane. If
// activeAt is null, then this contact has been purposefully hidden.
if (activeAt !== null) {
activeAt = activeAt || Date.now();
}
if (details.profileKey) {
conversation.set({profileKey: details.profileKey});
}
@ -384,7 +393,7 @@
name: details.name,
avatar: details.avatar,
color: details.color,
active_at: conversation.get('active_at') || Date.now(),
active_at: activeAt,
}).then(resolve, reject);
}).then(function() {
if (details.verified) {
@ -421,7 +430,13 @@
type: 'group',
};
if (details.active) {
updates.active_at = Date.now();
var activeAt = conversation.get('active_at');
// The idea is to make any new group show up in the left pane. If
// activeAt is null, then this group has been purposefully hidden.
if (activeAt !== null) {
updates.active_at = activeAt || Date.now();
}
} else {
updates.left = true;
}
@ -553,8 +568,7 @@
function onError(ev) {
var error = ev.error;
console.log(error);
console.log(error.stack);
console.log('background onError:', error && error.stack ? error.stack : error);
if (error.name === 'HTTPError' && (error.code == 401 || error.code == 403)) {
Whisper.Registration.remove();

View file

@ -38003,26 +38003,35 @@ var TextSecureServer = (function() {
rotateSignedPreKey: function() {
return this.queueTask(function() {
var signedKeyId = textsecure.storage.get('signedKeyId', 1);
if (typeof signedKeyId != 'number') {
throw new Error('Invalid signedKeyId');
}
var store = textsecure.storage.protocol;
var server = this.server;
var cleanSignedPreKeys = this.cleanSignedPreKeys;
return store.getIdentityKeyPair().then(function(identityKey) {
return libsignal.KeyHelper.generateSignedPreKey(identityKey, signedKeyId);
}).then(function(res) {
return server.setSignedPreKey({
keyId : res.keyId,
publicKey : res.keyPair.pubKey,
signature : res.signature
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),
]);
}).then(function() {
textsecure.storage.put('signedKeyId', signedKeyId + 1);
textsecure.storage.remove('signedKeyRotationRejected');
return store.storeSignedPreKey(res.keyId, res.keyPair).then(function() {
return cleanSignedPreKeys();
});
return cleanSignedPreKeys();
});
}).catch(function(e) {
console.log(
@ -38034,9 +38043,9 @@ var TextSecureServer = (function() {
var rejections = 1 + textsecure.storage.get('signedKeyRotationRejected', 0);
textsecure.storage.put('signedKeyRotationRejected', rejections);
console.log('Signed key rotation rejected count:', rejections);
} else {
throw e;
}
throw e;
});
}.bind(this));
},
@ -38045,35 +38054,72 @@ var TextSecureServer = (function() {
return this.pending = this.pending.then(taskWithTimeout, taskWithTimeout);
},
cleanSignedPreKeys: function() {
var nextSignedKeyId = textsecure.storage.get('signedKeyId');
if (typeof nextSignedKeyId != 'number') {
return Promise.resolve();
}
var activeSignedPreKeyId = nextSignedKeyId - 1;
var MINIMUM_KEYS = 3;
var store = textsecure.storage.protocol;
return store.loadSignedPreKeys().then(function(allRecords) {
var oldRecords = allRecords.filter(function(record) {
return record.keyId !== activeSignedPreKeyId;
});
oldRecords.sort(function(a, b) {
return store.loadSignedPreKeys().then(function(allKeys) {
allKeys.sort(function(a, b) {
return (a.created_at || 0) - (b.created_at || 0);
});
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;
});
console.log("Active signed prekey: " + activeSignedPreKeyId);
console.log("Old signed prekey record count: " + oldRecords.length);
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'
);
oldRecords.forEach(function(oldRecord) {
if ( oldRecord.keyId > activeSignedPreKeyId - 3 ) {
// keep at least the last 3 signed keys
var confirmedCount = confirmed.length;
// Keep MINIMUM_KEYS confirmed keys, then drop if older than a week
confirmed = confirmed.forEach(function(key, index) {
if (index < MINIMUM_KEYS) {
return;
}
var created_at = oldRecord.created_at || 0;
var archiveDuration = Date.now() - created_at;
if (archiveDuration > ARCHIVE_AGE) {
console.log("Removing signed prekey record:",
oldRecord.keyId, "with timestamp:", created_at);
store.removeSignedPreKey(oldRecord.keyId);
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);
}
});
});
@ -39054,8 +39100,7 @@ MessageReceiver.prototype.extend({
console.log('Got SyncMessage Request');
return this.removeFromCache(envelope);
} else if (syncMessage.read && syncMessage.read.length) {
console.log('read messages',
'from', envelope.source + '.' + envelope.sourceDevice);
console.log('read messages from', this.getEnvelopeId(envelope));
return this.handleRead(envelope, syncMessage.read);
} else if (syncMessage.verified) {
return this.handleVerified(envelope, syncMessage.verified);
@ -39166,6 +39211,7 @@ MessageReceiver.prototype.extend({
}.bind(this));
},
handleBlocked: function(envelope, blocked) {
console.log('Setting these numbers as blocked:', blocked.numbers);
textsecure.storage.put('blocked', blocked.numbers);
},
isBlocked: function(number) {
@ -39908,10 +39954,11 @@ MessageSender.prototype = {
var proto = textsecure.protobuf.DataMessage.decode(encodedMessage);
return new Promise(function(resolve, reject) {
this.sendMessageProto(timestamp, numbers, proto, function(res) {
if (res.errors.length > 0)
if (res.errors.length > 0) {
reject(res);
else
} else {
resolve(res);
}
});
}.bind(this));
},
@ -39963,7 +40010,9 @@ MessageSender.prototype = {
syncMessage.sent = sentMessage;
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
var silent = true;
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
},
getProfile: function(number) {
@ -39984,7 +40033,8 @@ MessageSender.prototype = {
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
var silent = true;
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
}
return Promise.resolve();
@ -40000,7 +40050,8 @@ MessageSender.prototype = {
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
var silent = true;
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
}
return Promise.resolve();
@ -40017,7 +40068,8 @@ MessageSender.prototype = {
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
var silent = true;
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
}
return Promise.resolve();
@ -40030,7 +40082,8 @@ MessageSender.prototype = {
var contentMessage = new textsecure.protobuf.Content();
contentMessage.receiptMessage = receiptMessage;
return this.sendIndividualProto(sender, contentMessage, Date.now(), true /*silent*/);
var silent = true;
return this.sendIndividualProto(sender, contentMessage, Date.now(), silent);
},
syncReadMessages: function(reads) {
var myNumber = textsecure.storage.user.getNumber();
@ -40047,7 +40100,8 @@ MessageSender.prototype = {
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
var silent = true;
return this.sendIndividualProto(myNumber, contentMessage, Date.now(), silent);
}
return Promise.resolve();
@ -40055,38 +40109,44 @@ MessageSender.prototype = {
syncVerification: function(destination, state, identityKey) {
var myNumber = textsecure.storage.user.getNumber();
var myDevice = textsecure.storage.user.getDeviceId();
if (myDevice != 1) {
// First send a null message to mask the sync message.
var nullMessage = new textsecure.protobuf.NullMessage();
var now = Date.now();
// Generate a random int from 1 and 512
var buffer = libsignal.crypto.getRandomBytes(1);
var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1;
// Generate a random padding buffer of the chosen size
nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength);
var contentMessage = new textsecure.protobuf.Content();
contentMessage.nullMessage = nullMessage;
return this.sendIndividualProto(destination, contentMessage, Date.now()).then(function() {
var verified = new textsecure.protobuf.Verified();
verified.state = state;
verified.destination = destination;
verified.identityKey = identityKey;
verified.nullMessage = nullMessage.padding;
var syncMessage = this.createSyncMessage();
syncMessage.verified = verified;
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
return this.sendIndividualProto(myNumber, contentMessage, Date.now());
}.bind(this));
if (myDevice === 1) {
return Promise.resolve();
}
return Promise.resolve();
// First send a null message to mask the sync message.
var nullMessage = new textsecure.protobuf.NullMessage();
// Generate a random int from 1 and 512
var buffer = libsignal.crypto.getRandomBytes(1);
var paddingLength = (new Uint8Array(buffer)[0] & 0x1ff) + 1;
// Generate a random padding buffer of the chosen size
nullMessage.padding = libsignal.crypto.getRandomBytes(paddingLength);
var contentMessage = new textsecure.protobuf.Content();
contentMessage.nullMessage = nullMessage;
// We want the NullMessage to look like a normal outgoing message; not silent
const promise = this.sendIndividualProto(destination, contentMessage, now);
return promise.then(function() {
var verified = new textsecure.protobuf.Verified();
verified.state = state;
verified.destination = destination;
verified.identityKey = identityKey;
verified.nullMessage = nullMessage.padding;
var syncMessage = this.createSyncMessage();
syncMessage.verified = verified;
var contentMessage = new textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
var silent = true;
return this.sendIndividualProto(myNumber, contentMessage, now, silent);
}.bind(this));
},
sendGroupProto: function(numbers, proto, timestamp) {
@ -40098,14 +40158,17 @@ MessageSender.prototype = {
}
return new Promise(function(resolve, reject) {
this.sendMessageProto(timestamp, numbers, proto, function(res) {
var silent = true;
var callback = function(res) {
res.dataMessage = proto.toArrayBuffer();
if (res.errors.length > 0) {
reject(res);
} else {
resolve(res);
}
}.bind(this));
}.bind(this);
this.sendMessageProto(timestamp, numbers, proto, callback, silent);
}.bind(this));
},

View file

@ -166,6 +166,10 @@ window.log = {
};
window.onerror = function(message, script, line, col, error) {
window.log.error(error.stack);
const errorInfo = error && error.stack ? error.stack : JSON.stringify(error);
window.log.error('Top-level unhandled error: ' + errorInfo);
};
window.addEventListener('unhandledrejection', function(rejectionEvent) {
window.log.error('Top-level unhandled promise rejection: ' + rejectionEvent.reason);
});

View file

@ -314,8 +314,11 @@
errors = [errors];
}
errors.forEach(function(e) {
console.log(e);
console.log(e.reason, e.stack);
console.log(
'Message.saveErrors:',
e && e.reason ? e.reason : null,
e && e.stack ? e.stack : e
);
});
errors = errors.map(function(e) {
if (e.constructor === Error ||

View file

@ -65,13 +65,12 @@
}
initComplete = true;
if (Whisper.Registration.isDone()) {
if (newVersion) {
runWhenOnline();
} else {
setTimeoutForNextRun();
}
events.on('registration_done', function() {
scheduleNextRotation();
setTimeoutForNextRun();
});
events.on('timetravel', function() {
if (Whisper.Registration.isDone()) {
setTimeoutForNextRun();

View file

@ -186,11 +186,15 @@
var prekey = new PreKey({id: keyId});
return new Promise(function(resolve) {
prekey.fetch().then(function() {
console.log('Successfully fetched prekey:', keyId);
resolve({
pubKey: prekey.attributes.publicKey,
privKey: prekey.attributes.privateKey
pubKey: prekey.get('publicKey'),
privKey: prekey.get('privateKey'),
});
}).fail(resolve);
}, function() {
console.log('Failed to load prekey:', keyId);
resolve();
});
});
},
storePreKey: function(keyId, keyPair) {
@ -211,7 +215,16 @@
this.trigger('removePreKey');
return new Promise(function(resolve) {
prekey.destroy().then(function() {
var deferred = prekey.destroy();
if (!deferred) {
return resolve();
}
return deferred.then(resolve, function(error) {
console.log(
'removePreKey error:',
error && error.stack ? error.stack : error
);
resolve();
});
});
@ -222,21 +235,23 @@
var prekey = new SignedPreKey({id: keyId});
return new Promise(function(resolve) {
prekey.fetch().then(function() {
console.log('Successfully loaded prekey:', prekey.get('id'));
resolve({
pubKey : prekey.get('publicKey'),
privKey : prekey.get('privateKey'),
created_at : prekey.get('created_at'),
keyId : prekey.get('id')
keyId : prekey.get('id'),
confirmed : prekey.get('confirmed'),
});
}).fail(function() {
console.log("Failed to load signed prekey:", keyId);
console.log('Failed to load signed prekey:', keyId);
resolve();
});
});
},
loadSignedPreKeys: function() {
if (arguments.length > 0) {
return Promise.reject(new Error("loadSignedPreKeys takes no arguments"));
return Promise.reject(new Error('loadSignedPreKeys takes no arguments'));
}
var signedPreKeys = new SignedPreKeyCollection();
return new Promise(function(resolve) {
@ -246,18 +261,20 @@
pubKey : prekey.get('publicKey'),
privKey : prekey.get('privateKey'),
created_at : prekey.get('created_at'),
keyId : prekey.get('id')
keyId : prekey.get('id'),
confirmed : prekey.get('confirmed'),
};
}));
});
});
},
storeSignedPreKey: function(keyId, keyPair) {
storeSignedPreKey: function(keyId, keyPair, confirmed) {
var prekey = new SignedPreKey({
id : keyId,
publicKey : keyPair.pubKey,
privateKey : keyPair.privKey,
created_at : Date.now()
created_at : Date.now(),
confirmed : Boolean(confirmed),
});
return new Promise(function(resolve) {
prekey.save().always(function() {