import * as Factory from '../lib/factory.js' import * as Collaboration from '../lib/Collaboration.js' chai.use(chaiAsPromised) const expect = chai.expect describe('shared vault crypto', function () { this.timeout(Factory.TwentySecondTimeout) let context afterEach(async function () { await context.deinit() localStorage.clear() }) beforeEach(async function () { localStorage.clear() context = await Factory.createAppContextWithRealCrypto() await context.launch() await context.register() }) describe('root key', () => { it('root key loaded from disk should have keypairs', async () => { const appIdentifier = context.identifier await context.deinit() let recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier) await recreatedContext.launch() expect(recreatedContext.encryption.getKeyPair()).to.not.be.undefined expect(recreatedContext.encryption.getSigningKeyPair()).to.not.be.undefined }) it('changing user password should re-encrypt all key system root keys', async () => { console.error('TODO: implement') }) it('changing user password should re-encrypt all trusted contacts', async () => { console.error('TODO: implement') }) }) describe('persistent content signature', () => { it('storage payloads should include signatureData', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await contactContext.changeNoteTitleAndSync(note, 'new title') await context.sync() const rawPayloads = await context.application.diskStorageService.getAllRawPayloads() const noteRawPayload = rawPayloads.find((payload) => payload.uuid === note.uuid) expect(noteRawPayload.signatureData).to.not.be.undefined await deinitContactContext() }) it('changing item content should erase existing signatureData', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await contactContext.changeNoteTitleAndSync(note, 'new title') await context.sync() let updatedNote = context.items.findItem(note.uuid) await context.changeNoteTitleAndSync(updatedNote, 'new title 2') updatedNote = context.items.findItem(note.uuid) expect(updatedNote.signatureData).to.be.undefined await deinitContactContext() }) it('encrypting an item into storage then loading it should verify authenticity of original content rather than most recent symmetric signature', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await contactContext.changeNoteTitleAndSync(note, 'new title') /** Override decrypt result to return failing signature */ const objectToSpy = context.encryption sinon.stub(objectToSpy, 'decryptSplit').callsFake(async (split) => { objectToSpy.decryptSplit.restore() const decryptedPayloads = await objectToSpy.decryptSplit(split) expect(decryptedPayloads.length).to.equal(1) const payload = decryptedPayloads[0] const mutatedPayload = new DecryptedPayload({ ...payload.ejected(), signatureData: { ...payload.signatureData, result: { ...payload.signatureData.result, passes: false, }, }, }) return [mutatedPayload] }) await context.sync() let updatedNote = context.items.findItem(note.uuid) expect(updatedNote.content.title).to.equal('new title') expect(updatedNote.signatureData.result.passes).to.equal(false) const appIdentifier = context.identifier await context.deinit() let recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier) await recreatedContext.launch() updatedNote = recreatedContext.items.findItem(note.uuid) expect(updatedNote.signatureData.result.passes).to.equal(false) /** Changing the content now should clear failing signature */ await recreatedContext.changeNoteTitleAndSync(updatedNote, 'new title 2') updatedNote = recreatedContext.items.findItem(note.uuid) expect(updatedNote.signatureData).to.be.undefined await recreatedContext.deinit() recreatedContext = await Factory.createAppContextWithRealCrypto(appIdentifier) await recreatedContext.launch() /** Decrypting from storage will now verify current user symmetric signature only */ updatedNote = recreatedContext.items.findItem(note.uuid) expect(updatedNote.signatureData.result.passes).to.equal(true) await recreatedContext.deinit() await deinitContactContext() }) }) describe('symmetrically encrypted items', () => { it('created items with a payload source of remote saved should not have signature data', async () => { const note = await context.createSyncedNote() expect(note.payload.source).to.equal(PayloadSource.RemoteSaved) expect(note.signatureData).to.be.undefined }) it('retrieved items that are then remote saved should have their signature data cleared', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await contactContext.changeNoteTitleAndSync(contactContext.items.findItem(note.uuid), 'new title') await context.sync() expect(context.items.findItem(note.uuid).signatureData).to.not.be.undefined await context.changeNoteTitleAndSync(context.items.findItem(note.uuid), 'new title') expect(context.items.findItem(note.uuid).signatureData).to.be.undefined await deinitContactContext() }) it('should allow client verification of authenticity of shared item changes', async () => { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) expect(context.contacts.isItemAuthenticallySigned(note)).to.equal('not-applicable') const contactNote = contactContext.items.findItem(note.uuid) expect(contactContext.contacts.isItemAuthenticallySigned(contactNote)).to.equal('yes') await contactContext.changeNoteTitleAndSync(contactNote, 'new title') await context.sync() let updatedNote = context.items.findItem(note.uuid) expect(context.contacts.isItemAuthenticallySigned(updatedNote)).to.equal('yes') await deinitContactContext() }) }) describe('keypair revocation', () => { it('should be able to revoke non-current keypair', async () => { console.error('TODO') }) it('revoking a keypair should send a keypair revocation event to trusted contacts', async () => { console.error('TODO') }) it('should not be able to revoke current key pair', async () => { console.error('TODO') }) it('should distrust revoked keypair as contact', async () => { console.error('TODO') }) }) })