210 lines
7.5 KiB
JavaScript
210 lines
7.5 KiB
JavaScript
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
|
|
|
|
beforeEach(async function () {
|
|
localStorage.clear()
|
|
|
|
context = await Factory.createVaultsContextWithRealCrypto()
|
|
|
|
await context.launch()
|
|
await context.register()
|
|
})
|
|
|
|
afterEach(async function () {
|
|
await context.deinit()
|
|
localStorage.clear()
|
|
sinon.restore()
|
|
})
|
|
|
|
describe('root key', () => {
|
|
it('root key loaded from disk should have keypairs', async () => {
|
|
const appIdentifier = context.identifier
|
|
await context.deinit()
|
|
|
|
let recreatedContext = await Factory.createVaultsContextWithRealCrypto(appIdentifier)
|
|
await recreatedContext.launch()
|
|
|
|
expect(recreatedContext.keyPair).to.not.be.undefined
|
|
expect(recreatedContext.signingKeyPair).to.not.be.undefined
|
|
|
|
await recreatedContext.deinit()
|
|
})
|
|
|
|
it('changing user password should re-encrypt all key system root keys and contacts with new user root key', async () => {
|
|
await Collaboration.createPrivateVault(context)
|
|
const spy = context.spyOnFunctionResult(context.application.sync, 'payloadsByPreparingForServer')
|
|
await context.changePassword('new_password')
|
|
|
|
const payloads = await spy
|
|
const keyPayloads = payloads.filter(
|
|
(payload) =>
|
|
payload.content_type === ContentType.TYPES.KeySystemRootKey ||
|
|
payload.content_type === ContentType.TYPES.TrustedContact,
|
|
)
|
|
expect(keyPayloads.length).to.equal(2)
|
|
|
|
for (const payload of payloads) {
|
|
const keyParams = context.encryption.getEmbeddedPayloadAuthenticatedData(new EncryptedPayload(payload)).kp
|
|
|
|
const userKeyParams = context.encryption.getRootKeyParams().content
|
|
expect(keyParams).to.eql(userKeyParams)
|
|
}
|
|
})
|
|
})
|
|
|
|
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.storage.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)
|
|
|
|
const contactNote = contactContext.items.findItem(note.uuid)
|
|
|
|
await contactContext.changeNoteTitleAndSync(contactNote, '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)
|
|
expect(decryptedPayloads[0].content_type).to.equal(ContentType.TYPES.Note)
|
|
|
|
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.createVaultsContextWithRealCrypto(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.createVaultsContextWithRealCrypto(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.getItemSignatureStatus(note)).to.equal(ItemSignatureValidationResult.NotApplicable)
|
|
|
|
const contactNote = contactContext.items.findItem(note.uuid)
|
|
|
|
expect(contactContext.contacts.getItemSignatureStatus(contactNote)).to.equal(
|
|
ItemSignatureValidationResult.Trusted,
|
|
)
|
|
|
|
await contactContext.changeNoteTitleAndSync(contactNote, 'new title')
|
|
|
|
await context.sync()
|
|
|
|
let updatedNote = context.items.findItem(note.uuid)
|
|
|
|
expect(context.contacts.getItemSignatureStatus(updatedNote)).to.equal(ItemSignatureValidationResult.Trusted)
|
|
|
|
await deinitContactContext()
|
|
})
|
|
})
|
|
})
|