Handle UNVERIFIED sync verification messages (via contact sync)

FREEBIE
pull/749/head
Scott Nonnenberg 8 years ago
parent 20451cc827
commit f654532fa8

@ -321,10 +321,12 @@
key: key key: key
}; };
if (state === 'DEFAULT') { if (state === 'VERIFIED') {
contact.setVerifiedDefault(options);
} else if (state === 'VERIFIED') {
contact.setVerified(options); contact.setVerified(options);
} else if (state === 'DEFAULT') {
contact.setVerifiedDefault(options);
} else {
contact.setUnverified(options);
} }
} }

@ -92,12 +92,19 @@
return this._setVerified(VERIFIED, options); return this._setVerified(VERIFIED, options);
}.bind(this)); }.bind(this));
}, },
setUnverified: function(options) {
var UNVERIFIED = this.verifiedEnum.UNVERIFIED;
return this.queueJob(function() {
return this._setVerified(UNVERIFIED, options);
}.bind(this));
},
_setVerified: function(verified, options) { _setVerified: function(verified, options) {
options = options || {}; options = options || {};
_.defaults(options, {viaSyncMessage: false, viaContactSync: false, key: null}); _.defaults(options, {viaSyncMessage: false, viaContactSync: false, key: null});
var VERIFIED = this.verifiedEnum.VERIFIED;
var DEFAULT = this.verifiedEnum.DEFAULT; var DEFAULT = this.verifiedEnum.DEFAULT;
var VERIFIED = this.verifiedEnum.VERIFIED;
var UNVERIFIED = this.verifiedEnum.UNVERIFIED;
if (!this.isPrivate()) { if (!this.isPrivate()) {
throw new Error('You cannot verify a group conversation. ' + throw new Error('You cannot verify a group conversation. ' +
@ -127,12 +134,13 @@
// 1) The message came from an explicit verification in another client (not // 1) The message came from an explicit verification in another client (not
// a contact sync) // a contact sync)
// 2) The verification value received by the contact sync is different // 2) The verification value received by the contact sync is different
// from what we have on record // from what we have on record (and it's not a transition to UNVERIFIED)
// 3) Our local verification status is not DEFAULT and it hasn't changed, // 3) Our local verification status is VERIFIED and it hasn't changed,
// but the key did change (say from Key1/Verified to Key2/Verified) // but the key did change (Key1/VERIFIED to Key2/VERIFIED - but we don't
// want to show DEFAULT->DEFAULT or UNVERIFIED->UNVERIFIED)
if (!options.viaContactSync if (!options.viaContactSync
|| beginningVerified !== verified || (beginningVerified !== verified && verified !== UNVERIFIED)
|| (keychange && verified !== DEFAULT)) { || (keychange && verified === VERIFIED)) {
var local = !options.viaSyncMessage && !options.viaContactSync; var local = !options.viaSyncMessage && !options.viaContactSync;
this.addVerifiedChange(this.id, verified === VERIFIED, {local: local}); this.addVerifiedChange(this.id, verified === VERIFIED, {local: local});

@ -579,6 +579,7 @@
}); });
}); });
}, },
// Resolves to true if a new identity key was saved
processVerifiedMessage: function(identifier, verifiedStatus, publicKey) { processVerifiedMessage: function(identifier, verifiedStatus, publicKey) {
if (identifier === null || identifier === undefined) { if (identifier === null || identifier === undefined) {
throw new Error("Tried to set verified for undefined/null key"); throw new Error("Tried to set verified for undefined/null key");
@ -599,26 +600,37 @@
isEqual = equalArrayBuffers(publicKey, identityRecord.get('publicKey')); isEqual = equalArrayBuffers(publicKey, identityRecord.get('publicKey'));
} }
}).always(function() { }).always(function() {
// Because new keys always start as DEFAULT, we don't need to create new record here
if (!isPresent && verifiedStatus === VerifiedStatus.DEFAULT) { if (!isPresent && verifiedStatus === VerifiedStatus.DEFAULT) {
console.log('No existing record for default status'); console.log('No existing record for default status');
resolve(); return resolve();
} }
// If we had a key before and it's the same, and we're not changing to
// VERIFIED, then it's a simple update of the verified flag.
if (isPresent && isEqual if (isPresent && isEqual
&& identityRecord.get('verified') !== VerifiedStatus.DEFAULT && identityRecord.get('verified') !== verifiedStatus
&& verifiedStatus === VerifiedStatus.DEFAULT) { && verifiedStatus !== VerifiedStatus.VERIFIED) {
textsecure.storage.protocol.setVerified( return textsecure.storage.protocol.setVerified(
identifier, verifiedStatus, publicKey identifier, verifiedStatus, publicKey
).then(resolve, reject); ).then(resolve, reject);
} }
if (verifiedStatus === VerifiedStatus.VERIFIED // We need to create a new record in three cases:
&& (!isPresent // 1. We had no key previously (checks above ensure that this is
|| (isPresent && !isEqual) // either VERIFIED/UNVERIFIED)
|| (isPresent && identityRecord.get('verified') !== VerifiedStatus.VERIFIED))) { // 2. We had a key before, but we got a new key
// (no matter the VERIFIED state)
// 3. It's the same key, but we weren't VERIFIED before and are now
// (checks above handle the situation when 'state != VERIFIED')
if (!isPresent
|| (isPresent && !isEqual)
|| (isPresent
&& identityRecord.get('verified') !== verifiedStatus
&& verifiedStatus === VerifiedStatus.VERIFIED)) {
textsecure.storage.protocol.saveIdentityWithAttributes(identifier, { return textsecure.storage.protocol.saveIdentityWithAttributes(identifier, {
publicKey : publicKey, publicKey : publicKey,
verified : verifiedStatus, verified : verifiedStatus,
firstUse : false, firstUse : false,
@ -636,6 +648,12 @@
return resolve(); return resolve();
}.bind(this), reject); }.bind(this), reject);
} }
// The situation which could get us here is:
// 1. had a previous key
// 2. new key is the same
// 3. desired new status is same as what we had before
return resolve();
}.bind(this)); }.bind(this));
}.bind(this)); }.bind(this));
}, },

@ -1,21 +1,27 @@
'use strict'; 'use strict';
describe("SignalProtocolStore", function() { describe("SignalProtocolStore", function() {
var identifier = '+5558675309';
var store;
var identityKey;
var testKey;
before(function(done) { before(function(done) {
store = textsecure.storage.protocol;
identityKey = {
pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32),
};
testKey = {
pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32),
};
storage.put('registrationId', 1337); storage.put('registrationId', 1337);
storage.put('identityKey', identityKey); storage.put('identityKey', identityKey);
storage.fetch().then(done, done); storage.fetch().then(done, done);
}); });
var store = textsecure.storage.protocol;
var identifier = '+5558675309';
var identityKey = {
pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32),
};
var testKey = {
pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32),
};
describe('getLocalRegistrationId', function() { describe('getLocalRegistrationId', function() {
it('retrieves my registration id', function(done) { it('retrieves my registration id', function(done) {
store.getLocalRegistrationId().then(function(reg) { store.getLocalRegistrationId().then(function(reg) {
@ -202,16 +208,21 @@ describe("SignalProtocolStore", function() {
}); });
}); });
describe('saveIdentityWithAttributes', function() { describe('saveIdentityWithAttributes', function() {
var now = Date.now(); var now;
var record = new IdentityKeyRecord({id: identifier}); var record;
var validAttributes = { var validAttributes;
publicKey : testKey.pubKey,
firstUse : true,
timestamp : now,
verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false
};
before(function(done) { before(function(done) {
now = Date.now();
record = new IdentityKeyRecord({id: identifier});
validAttributes = {
publicKey : testKey.pubKey,
firstUse : true,
timestamp : now,
verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false
};
store.removeIdentityKey(identifier).then(function() { done(); }); store.removeIdentityKey(identifier).then(function() { done(); });
}); });
describe('with valid attributes', function() { describe('with valid attributes', function() {
@ -346,27 +357,32 @@ describe("SignalProtocolStore", function() {
describe('processVerifiedMessage', function() { describe('processVerifiedMessage', function() {
var record; var record;
var newIdentity = libsignal.crypto.getRandomBytes(33); var newIdentity = libsignal.crypto.getRandomBytes(33);
function fetchRecord() { var keychangeTriggered;
function wrapDeferred(deferred) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
record.fetch().then(resolve, reject); return deferred.then(resolve, reject);
}); });
} }
function fetchRecord() {
return wrapDeferred(record.fetch());
}
beforeEach(function() {
keychangeTriggered = 0;
store.bind('keychange', function() {
keychangeTriggered++;
});
});
afterEach(function() {
store.unbind('keychange');
});
describe('when the new verified status is DEFAULT', function() { describe('when the new verified status is DEFAULT', function() {
describe('when there is no existing record', function() { describe('when there is no existing record', function() {
var keychangeTriggered;
before(function() { before(function() {
keychangeTriggered = 0;
store.bind('keychange', function() {
keychangeTriggered++;
});
record = new IdentityKeyRecord({ id: identifier }); record = new IdentityKeyRecord({ id: identifier });
return new Promise(function(resolve, reject) { return wrapDeferred(record.destroy());
record.destroy().then(resolve, reject);
});
});
after(function() {
store.unbind('keychange');
}); });
it ('does nothing', function() { it ('does nothing', function() {
@ -378,63 +394,174 @@ describe("SignalProtocolStore", function() {
throw new Error("processVerifiedMessage should not save new records"); throw new Error("processVerifiedMessage should not save new records");
}, function() { }, function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
return;
}); });
}); });
}); });
describe('when the record exists and is not DEFAULT and the key matches', function() { describe('when the record exists', function() {
var keychangeTriggered; describe('when the existing key is different', function() {
before(function() {
record = new IdentityKeyRecord({
id : identifier,
publicKey : testKey.pubKey,
firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false
});
return wrapDeferred(record.save());
});
before(function() { it ('saves the new identity and marks it DEFAULT', function() {
keychangeTriggered = 0; return store.processVerifiedMessage(
store.bind('keychange', function() { identifier, store.VerifiedStatus.DEFAULT, newIdentity
keychangeTriggered++; ).then(fetchRecord).then(function() {
assert.strictEqual(record.get('verified'), store.VerifiedStatus.DEFAULT);
assertEqualArrayBuffers(record.get('publicKey'), newIdentity);
assert.strictEqual(keychangeTriggered, 1);
});
}); });
record = new IdentityKeyRecord({ });
id : identifier, describe('when the existing key is the same but VERIFIED', function() {
publicKey : testKey.pubKey, before(function() {
firstUse : true, record = new IdentityKeyRecord({
timestamp : Date.now(), id : identifier,
verified : store.VerifiedStatus.VERIFIED, publicKey : testKey.pubKey,
nonblockingApproval : false firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false
});
return wrapDeferred(record.save());
}); });
return new Promise(function(resolve, reject) {
record.save().then(resolve, reject); it ('updates the verified status', function() {
return store.processVerifiedMessage(
identifier, store.VerifiedStatus.DEFAULT, testKey.pubKey
).then(fetchRecord).then(function() {
assert.strictEqual(record.get('verified'), store.VerifiedStatus.DEFAULT);
assertEqualArrayBuffers(record.get('publicKey'), testKey.pubKey);
assert.strictEqual(keychangeTriggered, 0);
});
}); });
}); });
after(function() { describe('when the existing key is the same and already DEFAULT', function() {
store.unbind('keychange'); before(function() {
record = new IdentityKeyRecord({
id : identifier,
publicKey : testKey.pubKey,
firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.DEFAULT,
nonblockingApproval : false
});
return wrapDeferred(record.save());
});
it ('does not hang', function() {
return store.processVerifiedMessage(
identifier, store.VerifiedStatus.DEFAULT, testKey.pubKey
).then(fetchRecord).then(function() {
assert.strictEqual(keychangeTriggered, 0);
});
});
});
});
});
describe('when the new verified status is UNVERIFIED', function() {
describe('when there is no existing record', function() {
before(function() {
record = new IdentityKeyRecord({ id: identifier });
return wrapDeferred(record.destroy());
}); });
it ('updates the verified status', function() { it ('saves the new identity and marks it verified', function() {
return store.processVerifiedMessage( return store.processVerifiedMessage(
identifier, store.VerifiedStatus.DEFAULT, testKey.pubKey identifier, store.VerifiedStatus.UNVERIFIED, newIdentity
).then(fetchRecord).then(function() { ).then(fetchRecord).then(function() {
assert.strictEqual(record.get('verified'), store.VerifiedStatus.DEFAULT); assert.strictEqual(record.get('verified'), store.VerifiedStatus.UNVERIFIED);
assertEqualArrayBuffers(record.get('publicKey'), testKey.pubKey); assertEqualArrayBuffers(record.get('publicKey'), newIdentity);
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
}); });
describe('when the record exists', function() {
describe('when the existing key is different', function() {
before(function() {
record = new IdentityKeyRecord({
id : identifier,
publicKey : testKey.pubKey,
firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false
});
return wrapDeferred(record.save());
});
it ('saves the new identity and marks it UNVERIFIED', function() {
return store.processVerifiedMessage(
identifier, store.VerifiedStatus.UNVERIFIED, newIdentity
).then(fetchRecord).then(function() {
assert.strictEqual(record.get('verified'), store.VerifiedStatus.UNVERIFIED);
assertEqualArrayBuffers(record.get('publicKey'), newIdentity);
assert.strictEqual(keychangeTriggered, 1);
});
});
});
describe('when the key exists and is DEFAULT', function() {
before(function() {
record = new IdentityKeyRecord({
id : identifier,
publicKey : testKey.pubKey,
firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.DEFAULT,
nonblockingApproval : false
});
return wrapDeferred(record.save());
});
it ('updates the verified status', function() {
return store.processVerifiedMessage(
identifier, store.VerifiedStatus.UNVERIFIED, testKey.pubKey
).then(fetchRecord).then(function() {
assert.strictEqual(record.get('verified'), store.VerifiedStatus.UNVERIFIED);
assertEqualArrayBuffers(record.get('publicKey'), testKey.pubKey);
assert.strictEqual(keychangeTriggered, 0);
});
});
});
describe('when the key exists and is already UNVERIFIED', function() {
before(function() {
record = new IdentityKeyRecord({
id : identifier,
publicKey : testKey.pubKey,
firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.UNVERIFIED,
nonblockingApproval : false
});
return wrapDeferred(record.save());
});
it ('does not hang', function() {
return store.processVerifiedMessage(
identifier, store.VerifiedStatus.UNVERIFIED, testKey.pubKey
).then(fetchRecord).then(function() {
assert.strictEqual(keychangeTriggered, 0);
});
});
});
});
}); });
describe('when the new verified status is VERIFIED', function() { describe('when the new verified status is VERIFIED', function() {
describe('when there is no existing record', function() { describe('when there is no existing record', function() {
var keychangeTriggered;
before(function() { before(function() {
keychangeTriggered = 0;
store.bind('keychange', function() {
keychangeTriggered++;
});
record = new IdentityKeyRecord({ id: identifier }); record = new IdentityKeyRecord({ id: identifier });
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
record.destroy().then(resolve, reject); record.destroy().then(resolve, reject);
}); });
}); });
after(function() {
store.unbind('keychange');
});
it ('saves the new identity and marks it verified', function() { it ('saves the new identity and marks it verified', function() {
return store.processVerifiedMessage( return store.processVerifiedMessage(
@ -448,14 +575,7 @@ describe("SignalProtocolStore", function() {
}); });
describe('when the record exists', function() { describe('when the record exists', function() {
describe('when the existing key is different', function() { describe('when the existing key is different', function() {
var keychangeTriggered;
before(function() { before(function() {
keychangeTriggered = 0;
store.bind('keychange', function() {
keychangeTriggered++;
});
record = new IdentityKeyRecord({ record = new IdentityKeyRecord({
id : identifier, id : identifier,
publicKey : testKey.pubKey, publicKey : testKey.pubKey,
@ -464,15 +584,10 @@ describe("SignalProtocolStore", function() {
verified : store.VerifiedStatus.VERIFIED, verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false nonblockingApproval : false
}); });
return new Promise(function(resolve, reject) { return wrapDeferred(record.save());
record.save().then(resolve, reject);
});
});
after(function() {
store.unbind('keychange');
}); });
it ('saves the new identity and marks it verified', function() { it ('saves the new identity and marks it VERIFIED', function() {
return store.processVerifiedMessage( return store.processVerifiedMessage(
identifier, store.VerifiedStatus.VERIFIED, newIdentity identifier, store.VerifiedStatus.VERIFIED, newIdentity
).then(fetchRecord).then(function() { ).then(fetchRecord).then(function() {
@ -482,15 +597,8 @@ describe("SignalProtocolStore", function() {
}); });
}); });
}); });
describe('when the existing key is the same and not verified', function() { describe('when the existing key is the same but UNVERIFIED', function() {
var keychangeTriggered;
before(function() { before(function() {
keychangeTriggered = 0;
store.bind('keychange', function() {
keychangeTriggered++;
});
record = new IdentityKeyRecord({ record = new IdentityKeyRecord({
id : identifier, id : identifier,
publicKey : testKey.pubKey, publicKey : testKey.pubKey,
@ -499,12 +607,7 @@ describe("SignalProtocolStore", function() {
verified : store.VerifiedStatus.UNVERIFIED, verified : store.VerifiedStatus.UNVERIFIED,
nonblockingApproval : false nonblockingApproval : false
}); });
return new Promise(function(resolve, reject) { return wrapDeferred(record.save());
record.save().then(resolve, reject);
});
});
after(function() {
store.unbind('keychange');
}); });
it ('saves the identity and marks it verified', function() { it ('saves the identity and marks it verified', function() {
@ -517,8 +620,30 @@ describe("SignalProtocolStore", function() {
}); });
}); });
}); });
describe('when the existing key is the same and already VERIFIED', function() {
before(function() {
record = new IdentityKeyRecord({
id : identifier,
publicKey : testKey.pubKey,
firstUse : true,
timestamp : Date.now(),
verified : store.VerifiedStatus.VERIFIED,
nonblockingApproval : false
});
return wrapDeferred(record.save());
});
it ('does not hang', function() {
return store.processVerifiedMessage(
identifier, store.VerifiedStatus.VERIFIED, testKey.pubKey
).then(fetchRecord).then(function() {
assert.strictEqual(keychangeTriggered, 0);
});
});
});
}); });
}); });
}); });
describe('getVerified', function() { describe('getVerified', function() {
before(function(done) { before(function(done) {

Loading…
Cancel
Save