You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			1036 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			1036 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
| /* global _, textsecure, libsignal, storage */
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| describe('SignalProtocolStore', () => {
 | |
|   const number = '+5558675309';
 | |
|   let store;
 | |
|   let identityKey;
 | |
|   let testKey;
 | |
| 
 | |
|   before(done => {
 | |
|     store = textsecure.storage.protocol;
 | |
|     store.hydrateCaches();
 | |
|     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)
 | |
|       .then(() => storage.put('identityKey', identityKey))
 | |
|       .then(() => storage.fetch())
 | |
|       .then(done, done);
 | |
|   });
 | |
| 
 | |
|   describe('getLocalRegistrationId', () => {
 | |
|     it('retrieves my registration id', async () => {
 | |
|       const id = await store.getLocalRegistrationId();
 | |
|       assert.strictEqual(id, 1337);
 | |
|     });
 | |
|   });
 | |
|   describe('getIdentityKeyPair', () => {
 | |
|     it('retrieves my identity key', async () => {
 | |
|       const key = await store.getIdentityKeyPair();
 | |
|       assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
 | |
|       assertEqualArrayBuffers(key.privKey, identityKey.privKey);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('saveIdentity', () => {
 | |
|     const address = new libsignal.SignalProtocolAddress(number, 1);
 | |
|     const identifier = address.toString();
 | |
| 
 | |
|     it('stores identity keys', async () => {
 | |
|       await store.saveIdentity(identifier, testKey.pubKey);
 | |
|       const key = await store.loadIdentityKey(number);
 | |
| 
 | |
|       assertEqualArrayBuffers(key, testKey.pubKey);
 | |
|     });
 | |
|     it('allows key changes', async () => {
 | |
|       const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|       await store.saveIdentity(identifier, testKey.pubKey);
 | |
|       await store.saveIdentity(identifier, newIdentity);
 | |
|     });
 | |
| 
 | |
|     describe('When there is no existing key (first use)', () => {
 | |
|       before(async () => {
 | |
|         await store.removeIdentityKey(number);
 | |
|         await store.saveIdentity(identifier, testKey.pubKey);
 | |
|       });
 | |
|       it('marks the key firstUse', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert(identity.firstUse);
 | |
|       });
 | |
|       it('sets the timestamp', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert(identity.timestamp);
 | |
|       });
 | |
|       it('sets the verified status to DEFAULT', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
 | |
|       });
 | |
|     });
 | |
|     describe('When there is a different existing key (non first use)', () => {
 | |
|       const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|       const oldTimestamp = Date.now();
 | |
| 
 | |
|       before(async () => {
 | |
|         await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|           id: identifier,
 | |
|           publicKey: testKey.pubKey,
 | |
|           firstUse: true,
 | |
|           timestamp: oldTimestamp,
 | |
|           nonblockingApproval: false,
 | |
|           verified: store.VerifiedStatus.DEFAULT,
 | |
|         });
 | |
| 
 | |
|         await store.hydrateCaches();
 | |
|         await store.saveIdentity(identifier, newIdentity);
 | |
|       });
 | |
|       it('marks the key not firstUse', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert(!identity.firstUse);
 | |
|       });
 | |
|       it('updates the timestamp', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.notEqual(identity.timestamp, oldTimestamp);
 | |
|       });
 | |
| 
 | |
|       describe('The previous verified status was DEFAULT', () => {
 | |
|         before(async () => {
 | |
|           await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|             id: number,
 | |
|             publicKey: testKey.pubKey,
 | |
|             firstUse: true,
 | |
|             timestamp: oldTimestamp,
 | |
|             nonblockingApproval: false,
 | |
|             verified: store.VerifiedStatus.DEFAULT,
 | |
|           });
 | |
|           await store.hydrateCaches();
 | |
| 
 | |
|           await store.saveIdentity(identifier, newIdentity);
 | |
|         });
 | |
|         it('sets the new key to default', async () => {
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|           assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
 | |
|         });
 | |
|       });
 | |
|       describe('The previous verified status was VERIFIED', () => {
 | |
|         before(async () => {
 | |
|           await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|             id: number,
 | |
|             publicKey: testKey.pubKey,
 | |
|             firstUse: true,
 | |
|             timestamp: oldTimestamp,
 | |
|             nonblockingApproval: false,
 | |
|             verified: store.VerifiedStatus.VERIFIED,
 | |
|           });
 | |
| 
 | |
|           await store.hydrateCaches();
 | |
|           await store.saveIdentity(identifier, newIdentity);
 | |
|         });
 | |
|         it('sets the new key to unverified', async () => {
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
| 
 | |
|           assert.strictEqual(
 | |
|             identity.verified,
 | |
|             store.VerifiedStatus.UNVERIFIED
 | |
|           );
 | |
|         });
 | |
|       });
 | |
|       describe('The previous verified status was UNVERIFIED', () => {
 | |
|         before(async () => {
 | |
|           await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|             id: number,
 | |
|             publicKey: testKey.pubKey,
 | |
|             firstUse: true,
 | |
|             timestamp: oldTimestamp,
 | |
|             nonblockingApproval: false,
 | |
|             verified: store.VerifiedStatus.UNVERIFIED,
 | |
|           });
 | |
| 
 | |
|           await store.hydrateCaches();
 | |
|           await store.saveIdentity(identifier, newIdentity);
 | |
|         });
 | |
|         it('sets the new key to unverified', async () => {
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|           assert.strictEqual(
 | |
|             identity.verified,
 | |
|             store.VerifiedStatus.UNVERIFIED
 | |
|           );
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|     describe('When the key has not changed', () => {
 | |
|       const oldTimestamp = Date.now();
 | |
|       before(async () => {
 | |
|         await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|           id: number,
 | |
|           publicKey: testKey.pubKey,
 | |
|           timestamp: oldTimestamp,
 | |
|           nonblockingApproval: false,
 | |
|           verified: store.VerifiedStatus.DEFAULT,
 | |
|         });
 | |
|         await store.hydrateCaches();
 | |
|       });
 | |
|       describe('If it is marked firstUse', () => {
 | |
|         before(async () => {
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|           identity.firstUse = true;
 | |
|           await window.Signal.Data.createOrUpdateIdentityKey(identity);
 | |
|           await store.hydrateCaches();
 | |
|         });
 | |
|         it('nothing changes', async () => {
 | |
|           await store.saveIdentity(identifier, testKey.pubKey, true);
 | |
| 
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|           assert(!identity.nonblockingApproval);
 | |
|           assert.strictEqual(identity.timestamp, oldTimestamp);
 | |
|         });
 | |
|       });
 | |
|       describe('If it is not marked firstUse', () => {
 | |
|         before(async () => {
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|           identity.firstUse = false;
 | |
|           await window.Signal.Data.createOrUpdateIdentityKey(identity);
 | |
|           await store.hydrateCaches();
 | |
|         });
 | |
|         describe('If nonblocking approval is required', () => {
 | |
|           let now;
 | |
|           before(async () => {
 | |
|             now = Date.now();
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
|             identity.timestamp = now;
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey(identity);
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
|           it('sets non-blocking approval', async () => {
 | |
|             await store.saveIdentity(identifier, testKey.pubKey, true);
 | |
| 
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(identity.nonblockingApproval, true);
 | |
|             assert.strictEqual(identity.timestamp, now);
 | |
|             assert.strictEqual(identity.firstUse, false);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
|   describe('saveIdentityWithAttributes', () => {
 | |
|     let now;
 | |
|     let validAttributes;
 | |
| 
 | |
|     before(async () => {
 | |
|       now = Date.now();
 | |
|       validAttributes = {
 | |
|         publicKey: testKey.pubKey,
 | |
|         firstUse: true,
 | |
|         timestamp: now,
 | |
|         verified: store.VerifiedStatus.VERIFIED,
 | |
|         nonblockingApproval: false,
 | |
|       };
 | |
| 
 | |
|       await store.removeIdentityKey(number);
 | |
|     });
 | |
|     describe('with valid attributes', () => {
 | |
|       before(async () => {
 | |
|         await store.saveIdentityWithAttributes(number, validAttributes);
 | |
|       });
 | |
| 
 | |
|       it('publicKey is saved', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|       });
 | |
|       it('firstUse is saved', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.firstUse, true);
 | |
|       });
 | |
|       it('timestamp is saved', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.timestamp, now);
 | |
|       });
 | |
|       it('verified is saved', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
 | |
|       });
 | |
|       it('nonblockingApproval is saved', async () => {
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.nonblockingApproval, false);
 | |
|       });
 | |
|     });
 | |
|     describe('with invalid attributes', () => {
 | |
|       let attributes;
 | |
|       beforeEach(() => {
 | |
|         attributes = _.clone(validAttributes);
 | |
|       });
 | |
| 
 | |
|       async function testInvalidAttributes() {
 | |
|         try {
 | |
|           await store.saveIdentityWithAttributes(number, attributes);
 | |
|           throw new Error('saveIdentityWithAttributes should have failed');
 | |
|         } catch (error) {
 | |
|           // good. we expect to fail with invalid attributes.
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       it('rejects an invalid publicKey', async () => {
 | |
|         attributes.publicKey = 'a string';
 | |
|         await testInvalidAttributes();
 | |
|       });
 | |
|       it('rejects invalid firstUse', async () => {
 | |
|         attributes.firstUse = 0;
 | |
|         await testInvalidAttributes();
 | |
|       });
 | |
|       it('rejects invalid timestamp', async () => {
 | |
|         attributes.timestamp = NaN;
 | |
|         await testInvalidAttributes();
 | |
|       });
 | |
|       it('rejects invalid verified', async () => {
 | |
|         attributes.verified = null;
 | |
|         await testInvalidAttributes();
 | |
|       });
 | |
|       it('rejects invalid nonblockingApproval', async () => {
 | |
|         attributes.nonblockingApproval = 0;
 | |
|         await testInvalidAttributes();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
|   describe('setApproval', () => {
 | |
|     it('sets nonblockingApproval', async () => {
 | |
|       await store.setApproval(number, true);
 | |
|       const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
| 
 | |
|       assert.strictEqual(identity.nonblockingApproval, true);
 | |
|     });
 | |
|   });
 | |
|   describe('setVerified', () => {
 | |
|     async function saveRecordDefault() {
 | |
|       await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|         id: number,
 | |
|         publicKey: testKey.pubKey,
 | |
|         firstUse: true,
 | |
|         timestamp: Date.now(),
 | |
|         verified: store.VerifiedStatus.DEFAULT,
 | |
|         nonblockingApproval: false,
 | |
|       });
 | |
|       await store.hydrateCaches();
 | |
|     }
 | |
|     describe('with no public key argument', () => {
 | |
|       before(saveRecordDefault);
 | |
|       it('updates the verified status', async () => {
 | |
|         await store.setVerified(number, store.VerifiedStatus.VERIFIED);
 | |
| 
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
 | |
|         assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|       });
 | |
|     });
 | |
|     describe('with the current public key', () => {
 | |
|       before(saveRecordDefault);
 | |
|       it('updates the verified status', async () => {
 | |
|         await store.setVerified(
 | |
|           number,
 | |
|           store.VerifiedStatus.VERIFIED,
 | |
|           testKey.pubKey
 | |
|         );
 | |
| 
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
 | |
|         assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|       });
 | |
|     });
 | |
|     describe('with a mismatching public key', () => {
 | |
|       const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|       before(saveRecordDefault);
 | |
|       it('does not change the record.', async () => {
 | |
|         await store.setVerified(
 | |
|           number,
 | |
|           store.VerifiedStatus.VERIFIED,
 | |
|           newIdentity
 | |
|         );
 | |
| 
 | |
|         const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
|         assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
 | |
|         assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
|   describe('processContactSyncVerificationState', () => {
 | |
|     const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|     let keychangeTriggered;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       keychangeTriggered = 0;
 | |
|       store.bind('keychange', () => {
 | |
|         keychangeTriggered += 1;
 | |
|       });
 | |
|     });
 | |
|     afterEach(() => {
 | |
|       store.unbind('keychange');
 | |
|     });
 | |
| 
 | |
|     describe('when the new verified status is DEFAULT', () => {
 | |
|       describe('when there is no existing record', () => {
 | |
|         before(async () => {
 | |
|           await window.Signal.Data.removeIdentityKeyById(number);
 | |
|           await store.hydrateCaches();
 | |
|         });
 | |
| 
 | |
|         it('does nothing', async () => {
 | |
|           await store.processContactSyncVerificationState(
 | |
|             number,
 | |
|             store.VerifiedStatus.DEFAULT,
 | |
|             newIdentity
 | |
|           );
 | |
| 
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
| 
 | |
|           if (identity) {
 | |
|             // fetchRecord resolved so there is a record.
 | |
|             // Bad.
 | |
|             throw new Error(
 | |
|               'processContactSyncVerificationState should not save new records'
 | |
|             );
 | |
|           }
 | |
| 
 | |
|           assert.strictEqual(keychangeTriggered, 0);
 | |
|         });
 | |
|       });
 | |
|       describe('when the record exists', () => {
 | |
|         describe('when the existing key is different', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.VERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('does not save the new identity (because this is a less secure state)', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.DEFAULT,
 | |
|               newIdentity
 | |
|             );
 | |
| 
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(
 | |
|               identity.verified,
 | |
|               store.VerifiedStatus.VERIFIED
 | |
|             );
 | |
|             assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|         describe('when the existing key is the same but VERIFIED', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.VERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('updates the verified status', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.DEFAULT,
 | |
|               testKey.pubKey
 | |
|             );
 | |
| 
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
 | |
|             assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|         describe('when the existing key is the same and already DEFAULT', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.DEFAULT,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('does not hang', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.DEFAULT,
 | |
|               testKey.pubKey
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|     describe('when the new verified status is UNVERIFIED', () => {
 | |
|       describe('when there is no existing record', () => {
 | |
|         before(async () => {
 | |
|           await window.Signal.Data.removeIdentityKeyById(number);
 | |
|           await store.hydrateCaches();
 | |
|         });
 | |
| 
 | |
|         it('saves the new identity and marks it verified', async () => {
 | |
|           await store.processContactSyncVerificationState(
 | |
|             number,
 | |
|             store.VerifiedStatus.UNVERIFIED,
 | |
|             newIdentity
 | |
|           );
 | |
| 
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
| 
 | |
|           assert.strictEqual(
 | |
|             identity.verified,
 | |
|             store.VerifiedStatus.UNVERIFIED
 | |
|           );
 | |
|           assertEqualArrayBuffers(identity.publicKey, newIdentity);
 | |
|           assert.strictEqual(keychangeTriggered, 0);
 | |
|         });
 | |
|       });
 | |
|       describe('when the record exists', () => {
 | |
|         describe('when the existing key is different', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.VERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('saves the new identity and marks it UNVERIFIED', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.UNVERIFIED,
 | |
|               newIdentity
 | |
|             );
 | |
| 
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(
 | |
|               identity.verified,
 | |
|               store.VerifiedStatus.UNVERIFIED
 | |
|             );
 | |
|             assertEqualArrayBuffers(identity.publicKey, newIdentity);
 | |
|             assert.strictEqual(keychangeTriggered, 1);
 | |
|           });
 | |
|         });
 | |
|         describe('when the key exists and is DEFAULT', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.DEFAULT,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('updates the verified status', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.UNVERIFIED,
 | |
|               testKey.pubKey
 | |
|             );
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(
 | |
|               identity.verified,
 | |
|               store.VerifiedStatus.UNVERIFIED
 | |
|             );
 | |
|             assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|         describe('when the key exists and is already UNVERIFIED', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.UNVERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('does not hang', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.UNVERIFIED,
 | |
|               testKey.pubKey
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|     describe('when the new verified status is VERIFIED', () => {
 | |
|       describe('when there is no existing record', () => {
 | |
|         before(async () => {
 | |
|           await window.Signal.Data.removeIdentityKeyById(number);
 | |
|           await store.hydrateCaches();
 | |
|         });
 | |
| 
 | |
|         it('saves the new identity and marks it verified', async () => {
 | |
|           await store.processContactSyncVerificationState(
 | |
|             number,
 | |
|             store.VerifiedStatus.VERIFIED,
 | |
|             newIdentity
 | |
|           );
 | |
|           const identity = await window.Signal.Data.getIdentityKeyById(number);
 | |
| 
 | |
|           assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
 | |
|           assertEqualArrayBuffers(identity.publicKey, newIdentity);
 | |
|           assert.strictEqual(keychangeTriggered, 0);
 | |
|         });
 | |
|       });
 | |
|       describe('when the record exists', () => {
 | |
|         describe('when the existing key is different', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.VERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('saves the new identity and marks it VERIFIED', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.VERIFIED,
 | |
|               newIdentity
 | |
|             );
 | |
| 
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(
 | |
|               identity.verified,
 | |
|               store.VerifiedStatus.VERIFIED
 | |
|             );
 | |
|             assertEqualArrayBuffers(identity.publicKey, newIdentity);
 | |
|             assert.strictEqual(keychangeTriggered, 1);
 | |
|           });
 | |
|         });
 | |
|         describe('when the existing key is the same but UNVERIFIED', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.UNVERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('saves the identity and marks it verified', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.VERIFIED,
 | |
|               testKey.pubKey
 | |
|             );
 | |
|             const identity = await window.Signal.Data.getIdentityKeyById(
 | |
|               number
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(
 | |
|               identity.verified,
 | |
|               store.VerifiedStatus.VERIFIED
 | |
|             );
 | |
|             assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|         describe('when the existing key is the same and already VERIFIED', () => {
 | |
|           before(async () => {
 | |
|             await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|               id: number,
 | |
|               publicKey: testKey.pubKey,
 | |
|               firstUse: true,
 | |
|               timestamp: Date.now(),
 | |
|               verified: store.VerifiedStatus.VERIFIED,
 | |
|               nonblockingApproval: false,
 | |
|             });
 | |
|             await store.hydrateCaches();
 | |
|           });
 | |
| 
 | |
|           it('does not hang', async () => {
 | |
|             await store.processContactSyncVerificationState(
 | |
|               number,
 | |
|               store.VerifiedStatus.VERIFIED,
 | |
|               testKey.pubKey
 | |
|             );
 | |
| 
 | |
|             assert.strictEqual(keychangeTriggered, 0);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('isUntrusted', () => {
 | |
|     it('returns false if identity key old enough', async () => {
 | |
|       await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|         id: number,
 | |
|         publicKey: testKey.pubKey,
 | |
|         timestamp: Date.now() - 10 * 1000 * 60,
 | |
|         verified: store.VerifiedStatus.DEFAULT,
 | |
|         firstUse: false,
 | |
|         nonblockingApproval: false,
 | |
|       });
 | |
| 
 | |
|       await store.hydrateCaches();
 | |
|       const untrusted = await store.isUntrusted(number);
 | |
|       assert.strictEqual(untrusted, false);
 | |
|     });
 | |
| 
 | |
|     it('returns false if new but nonblockingApproval is true', async () => {
 | |
|       await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|         id: number,
 | |
|         publicKey: testKey.pubKey,
 | |
|         timestamp: Date.now(),
 | |
|         verified: store.VerifiedStatus.DEFAULT,
 | |
|         firstUse: false,
 | |
|         nonblockingApproval: true,
 | |
|       });
 | |
|       await store.hydrateCaches();
 | |
| 
 | |
|       const untrusted = await store.isUntrusted(number);
 | |
|       assert.strictEqual(untrusted, false);
 | |
|     });
 | |
| 
 | |
|     it('returns false if new but firstUse is true', async () => {
 | |
|       await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|         id: number,
 | |
|         publicKey: testKey.pubKey,
 | |
|         timestamp: Date.now(),
 | |
|         verified: store.VerifiedStatus.DEFAULT,
 | |
|         firstUse: true,
 | |
|         nonblockingApproval: false,
 | |
|       });
 | |
|       await store.hydrateCaches();
 | |
| 
 | |
|       const untrusted = await store.isUntrusted(number);
 | |
|       assert.strictEqual(untrusted, false);
 | |
|     });
 | |
| 
 | |
|     it('returns true if new, and no flags are set', async () => {
 | |
|       await window.Signal.Data.createOrUpdateIdentityKey({
 | |
|         id: number,
 | |
|         publicKey: testKey.pubKey,
 | |
|         timestamp: Date.now(),
 | |
|         verified: store.VerifiedStatus.DEFAULT,
 | |
|         firstUse: false,
 | |
|         nonblockingApproval: false,
 | |
|       });
 | |
|       await store.hydrateCaches();
 | |
| 
 | |
|       const untrusted = await store.isUntrusted(number);
 | |
|       assert.strictEqual(untrusted, true);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('getVerified', () => {
 | |
|     before(async () => {
 | |
|       await store.setVerified(number, store.VerifiedStatus.VERIFIED);
 | |
|     });
 | |
|     it('resolves to the verified status', async () => {
 | |
|       const result = await store.getVerified(number);
 | |
|       assert.strictEqual(result, store.VerifiedStatus.VERIFIED);
 | |
|     });
 | |
|   });
 | |
|   describe('isTrustedIdentity', () => {
 | |
|     const address = new libsignal.SignalProtocolAddress(number, 1);
 | |
|     const identifier = address.toString();
 | |
| 
 | |
|     describe('When invalid direction is given', () => {
 | |
|       it('should fail', async () => {
 | |
|         try {
 | |
|           await store.isTrustedIdentity(number, testKey.pubKey);
 | |
|           throw new Error('isTrustedIdentity should have failed');
 | |
|         } catch (error) {
 | |
|           // good
 | |
|         }
 | |
|       });
 | |
|     });
 | |
|     describe('When direction is RECEIVING', () => {
 | |
|       it('always returns true', async () => {
 | |
|         const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|         await store.saveIdentity(identifier, testKey.pubKey);
 | |
| 
 | |
|         const trusted = await store.isTrustedIdentity(
 | |
|           identifier,
 | |
|           newIdentity,
 | |
|           store.Direction.RECEIVING
 | |
|         );
 | |
| 
 | |
|         if (!trusted) {
 | |
|           throw new Error('isTrusted returned false when receiving');
 | |
|         }
 | |
|       });
 | |
|     });
 | |
|     describe('When direction is SENDING', () => {
 | |
|       describe('When there is no existing key (first use)', () => {
 | |
|         before(async () => {
 | |
|           await store.removeIdentityKey(number);
 | |
|         });
 | |
|         it('returns true', async () => {
 | |
|           const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|           const trusted = await store.isTrustedIdentity(
 | |
|             identifier,
 | |
|             newIdentity,
 | |
|             store.Direction.SENDING
 | |
|           );
 | |
|           if (!trusted) {
 | |
|             throw new Error('isTrusted returned false on first use');
 | |
|           }
 | |
|         });
 | |
|       });
 | |
|       describe('When there is an existing key', () => {
 | |
|         before(async () => {
 | |
|           await store.saveIdentity(identifier, testKey.pubKey);
 | |
|         });
 | |
|         describe('When the existing key is different', () => {
 | |
|           it('returns false', async () => {
 | |
|             const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|             const trusted = await store.isTrustedIdentity(
 | |
|               identifier,
 | |
|               newIdentity,
 | |
|               store.Direction.SENDING
 | |
|             );
 | |
|             if (trusted) {
 | |
|               throw new Error('isTrusted returned true on untrusted key');
 | |
|             }
 | |
|           });
 | |
|         });
 | |
|         describe('When the existing key matches the new key', () => {
 | |
|           const newIdentity = libsignal.crypto.getRandomBytes(33);
 | |
|           before(async () => {
 | |
|             await store.saveIdentity(identifier, newIdentity);
 | |
|           });
 | |
|           it('returns false if keys match but we just received this new identiy', async () => {
 | |
|             const trusted = await store.isTrustedIdentity(
 | |
|               identifier,
 | |
|               newIdentity,
 | |
|               store.Direction.SENDING
 | |
|             );
 | |
| 
 | |
|             if (trusted) {
 | |
|               throw new Error('isTrusted returned true on untrusted key');
 | |
|             }
 | |
|           });
 | |
|           it('returns true if we have already approved identity', async () => {
 | |
|             await store.saveIdentity(identifier, newIdentity, true);
 | |
| 
 | |
|             const trusted = await store.isTrustedIdentity(
 | |
|               identifier,
 | |
|               newIdentity,
 | |
|               store.Direction.SENDING
 | |
|             );
 | |
|             if (!trusted) {
 | |
|               throw new Error('isTrusted returned false on an approved key');
 | |
|             }
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
|   describe('storePreKey', () => {
 | |
|     it('stores prekeys', async () => {
 | |
|       await store.storePreKey(1, testKey);
 | |
|       const key = await store.loadPreKey(1);
 | |
|       assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
 | |
|       assertEqualArrayBuffers(key.privKey, testKey.privKey);
 | |
|     });
 | |
|   });
 | |
|   describe('removePreKey', () => {
 | |
|     before(async () => {
 | |
|       await store.storePreKey(2, testKey);
 | |
|     });
 | |
|     it('deletes prekeys', async () => {
 | |
|       await store.removePreKey(2, testKey);
 | |
| 
 | |
|       const key = await store.loadPreKey(2);
 | |
|       assert.isUndefined(key);
 | |
|     });
 | |
|   });
 | |
|   describe('storeSignedPreKey', () => {
 | |
|     it('stores signed prekeys', async () => {
 | |
|       await store.storeSignedPreKey(3, testKey);
 | |
| 
 | |
|       const key = await store.loadSignedPreKey(3);
 | |
|       assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
 | |
|       assertEqualArrayBuffers(key.privKey, testKey.privKey);
 | |
|     });
 | |
|   });
 | |
|   describe('removeSignedPreKey', () => {
 | |
|     before(async () => {
 | |
|       await store.storeSignedPreKey(4, testKey);
 | |
|     });
 | |
|     it('deletes signed prekeys', async () => {
 | |
|       await store.removeSignedPreKey(4, testKey);
 | |
| 
 | |
|       const key = await store.loadSignedPreKey(4);
 | |
|       assert.isUndefined(key);
 | |
|     });
 | |
|   });
 | |
|   describe('storeSession', () => {
 | |
|     it('stores sessions', async () => {
 | |
|       const testRecord = 'an opaque string';
 | |
| 
 | |
|       await store.storeSession(`${number}.1`, testRecord);
 | |
|       const record = await store.loadSession(`${number}.1`);
 | |
| 
 | |
|       assert.deepEqual(record, testRecord);
 | |
|     });
 | |
|   });
 | |
|   describe('removeAllSessions', () => {
 | |
|     it('removes all sessions for a number', async () => {
 | |
|       const testRecord = 'an opaque string';
 | |
|       const devices = [1, 2, 3].map(deviceId => {
 | |
|         return [number, deviceId].join('.');
 | |
|       });
 | |
| 
 | |
|       await Promise.all(
 | |
|         devices.map(async encodedNumber => {
 | |
|           await store.storeSession(encodedNumber, testRecord + encodedNumber);
 | |
|         })
 | |
|       );
 | |
| 
 | |
|       await store.removeAllSessions(number);
 | |
| 
 | |
|       const records = await Promise.all(
 | |
|         devices.map(store.loadSession.bind(store))
 | |
|       );
 | |
| 
 | |
|       for (let i = 0, max = records.length; i < max; i += 1) {
 | |
|         assert.isUndefined(records[i]);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
|   describe('clearSessionStore', () => {
 | |
|     it('clears the session store', async () => {
 | |
|       const testRecord = 'an opaque string';
 | |
|       await store.storeSession(`${number}.1`, testRecord);
 | |
|       await store.clearSessionStore();
 | |
| 
 | |
|       const record = await store.loadSession(`${number}.1`);
 | |
|       assert.isUndefined(record);
 | |
|     });
 | |
|   });
 | |
|   describe('getDeviceIds', () => {
 | |
|     it('returns deviceIds for a number', async () => {
 | |
|       const testRecord = 'an opaque string';
 | |
|       const devices = [1, 2, 3, 10].map(deviceId => {
 | |
|         return [number, deviceId].join('.');
 | |
|       });
 | |
| 
 | |
|       await Promise.all(
 | |
|         devices.map(async encodedNumber => {
 | |
|           await store.storeSession(encodedNumber, testRecord + encodedNumber);
 | |
|         })
 | |
|       );
 | |
| 
 | |
|       const deviceIds = await store.getDeviceIds(number);
 | |
|       assert.sameMembers(deviceIds, [1, 2, 3, 10]);
 | |
|     });
 | |
|     it('returns empty array for a number with no device ids', async () => {
 | |
|       const deviceIds = await store.getDeviceIds('foo');
 | |
|       assert.sameMembers(deviceIds, []);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('Not yet processed messages', () => {
 | |
|     beforeEach(async () => {
 | |
|       await store.removeAllUnprocessed();
 | |
|       const items = await store.getAllUnprocessed();
 | |
|       assert.strictEqual(items.length, 0);
 | |
|     });
 | |
| 
 | |
|     it('adds three and gets them back', async () => {
 | |
|       await Promise.all([
 | |
|         store.addUnprocessed({ id: 2, envelope: 'second', timestamp: 2 }),
 | |
|         store.addUnprocessed({ id: 3, envelope: 'third', timestamp: 3 }),
 | |
|         store.addUnprocessed({ id: 1, envelope: 'first', timestamp: 1 }),
 | |
|       ]);
 | |
| 
 | |
|       const items = await store.getAllUnprocessed();
 | |
|       assert.strictEqual(items.length, 3);
 | |
| 
 | |
|       // they are in the proper order because the collection comparator is 'timestamp'
 | |
|       assert.strictEqual(items[0].envelope, 'first');
 | |
|       assert.strictEqual(items[1].envelope, 'second');
 | |
|       assert.strictEqual(items[2].envelope, 'third');
 | |
|     });
 | |
| 
 | |
|     it('saveUnprocessed successfully updates item', async () => {
 | |
|       const id = 1;
 | |
|       await store.addUnprocessed({ id, envelope: 'first', timestamp: 1 });
 | |
|       await store.updateUnprocessedWithData(id, { decrypted: 'updated' });
 | |
| 
 | |
|       const items = await store.getAllUnprocessed();
 | |
|       assert.strictEqual(items.length, 1);
 | |
|       assert.strictEqual(items[0].decrypted, 'updated');
 | |
|       assert.strictEqual(items[0].timestamp, 1);
 | |
|     });
 | |
| 
 | |
|     it('removeUnprocessed successfully deletes item', async () => {
 | |
|       const id = 1;
 | |
|       await store.addUnprocessed({ id, envelope: 'first', timestamp: 1 });
 | |
|       await store.removeUnprocessed(id);
 | |
| 
 | |
|       const items = await store.getAllUnprocessed();
 | |
|       assert.strictEqual(items.length, 0);
 | |
|     });
 | |
|   });
 | |
| });
 |